1.简述
SpringBoot因为内置了tomcat或jetty服务器,不需要直接部署War文件,所以SpringBoot的程序起点是一个普通的主函数。
主函数如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringBoot的启动过程都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。
启动的过程可以概括为:
- 通过SpringFactoriesLoader加载META-INF/spring.factories文件,获取并创建SpringApplicationRunListener对象。
- 然后由SpringApplicationRunListener 来发出starting消息。
- 创建参数,并配置当前SpringBoot 应用将要使用的Environment。
- 完成之后,依然由SpringApplicationRunListener来发出environmentPrepared 消息。
- 创建ApplicationContext。
- 初始化ApplicationContext,并设置Environment,加载相关配置等。
- 由SpringApplicationRunListener来发出contextPrepared消息,告知SpringBoot应用使用的ApplicationContext已准备OK。
- 将各种beans装载入ApplicationContext,继续由SpringApplicationRunListener来发出contextLoaded消息,告知SpringBoot应用使用的ApplicationContext已装填OK。
- refresh ApplicationContext,完成IoC容器可用的最后一步
- 由SpringApplicationRunListener来发出started消息。
- 完成最终的程序的启动。
- 由SpringApplicationRunListener来发出running消息,告知程序已运行起来了。
2.源码分析
(1)@SpringBootApplication注解
@SpringBootApplication注解的源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { //这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter不知道用来干什么的
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//等同于EnableAutoConfiguration注解的exclude属性
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
//等同于EnableAutoConfiguration注解的excludeName属性
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
//等同于ComponentScan注解的basePackages属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//等同于ComponentScan注解的basePackageClasses属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
可以看到@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,由以下三个注解组成:
- @SpringBootConfiguration:来源于 @Configuration,二者功能都是将当前类标注为配置类,并将当前类里以@Bean 注解标记的方法的实例注入到srping容器中。
- @EnableAutoConfiguration:启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前 IoC 容器之中。
- @ComponentScan:对应于XML配置形式中的 context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:@Controller @Entity @Component @Service @Repository。
因此@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。
(2)创建SpringApplication对象
SpringApplication类的run方法,这个方法就做了2件事:一是创建SpringApplication对象,二是启动SpringApplication。
SpringApplication构造器源码如下:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
//断言primarySources不能为null,如果为null,抛出异常提示
Assert.notNull(primarySources, "PrimarySources must not be null");
//启动类传入的Class
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
//设置ApplicationContextInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//判断主类,初始化入口类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//判断主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
在构造器里主要就做了2件事,1是设置初始化器,2是设置监听器。
设置初始化器的源码如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
//从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//将names的对象实例化
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。
设置监听器:和设置初始化器一个样,都是通过getSpringFactoriesInstances函数实例化监听器。
创建了SpringApplication实例之后,就完成了SpringApplication类的初始化工作。
(3)run方法
得到SpringApplication实例后,接下来就调用实例方法run()。
run方法源码如下:
public ConfigurableApplicationContext run(String... args) {
//创建计时器
StopWatch stopWatch = new StopWatch();
//开始计时
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
//定义上下文对象
ConfigurableApplicationContext context = null;
//Headless模式设置
this.configureHeadlessProperty();
//加载SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//发送ApplicationStartingEvent事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//封装ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置环境模块
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//根据环境信息配置要忽略的bean信息
this.configureIgnoreBeanInfo(environment);
//打印Banner标志
Banner printedBanner = this.printBanner(environment);
//创建ApplicationContext应用上下文
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//ApplicationContext基本属性配置
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
this.refreshContext(context);
//刷新后的操作,由子类去扩展
this.afterRefresh(context, applicationArguments);
//计时结束
stopWatch.stop();
//打印日志
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
listeners.started(context);
//查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
//发送ApplicationFailedEvent事件,标志SpringBoot启动失败
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
//发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
listeners.running(context);
return context;
} catch (Throwable var9) {
//报告异常,但是不发送任何事件
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
执行完run方法后,SpringBoot就启动完成了。
3.总结
启动类看起来就一个@SpringBootApplication注解,一个run()方法。其实是经过高度封装后的。从这个分析中学到很多东西。例如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。