启动过程
三个要点:
- 启动类(SpringbootdemoApplication)上面的注解
- 创建SpringApplication对象
- run方法
注解:
@Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = { // 扫描路径设置
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
@SpringbootConfiguration:
表示这是一个配置类,可以被@ComponentScan扫描到,程序中只能有一个,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。(springboot的注解,spring的注解是Configuration)
@ComponentScan:
@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
@Component下的子注解:@Service,@Repository,@Controller;默认会扫描当前包和所有子包。
@EnableAutoConfiguration:
- AutoConfigurationPackage
- @Import(AutoConfigurationPackages.Registrar.class)
- AutoConfigurationPackages类中的getPackageName()返回了当前主程序类的同级以及子级的包组件,这也就是为什么,我们要把DemoApplication放在项目的最高级中
- Registrar将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器
- @Import(AutoConfigurationPackages.Registrar.class)
- @Import(AutoConfigurationImportSelector.class)
- AutoConfigurationImportSelector继承了 ImportSelector,ImportSelector中的selectImports方法,该方法的返回值通过反射会被纳入到spring容器管理(其返回值是全限定java类名(字符串))。其核心功能是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,经过删选之后(去重—List转Set再转List,从注解的exclude/excludeName属性中获取排除项进行排除),最终得到用于Import的configuration和exclusion
- selectImports方法中
List configurations = getCandidateConfigurations(annotationMetadata,attributes)
就会调用SpringFactoriesLoader的方法,可以从classpath中搜索META-INF/spring.factories配置文件,并读取配置(获得之后进行删除选)
- selectImports方法中
- AutoConfigurationImportSelector继承了 ImportSelector,ImportSelector中的selectImports方法,该方法的返回值通过反射会被纳入到spring容器管理(其返回值是全限定java类名(字符串))。其核心功能是获取spring.factories中EnableAutoConfiguration所对应的Configuration类列表,经过删选之后(去重—List转Set再转List,从注解的exclude/excludeName属性中获取排除项进行排除),最终得到用于Import的configuration和exclusion
创建SpringApplication对象
https://segmentfault.com/a/1190000019560001
-
推断应用程序的类型,进而根据应用程序的类型创建恰当的ApplicationContext,默认的应用类型有三种:非web环境、web环境、reactive环境三种
-
初始化ApplicationContextinitializer(初始化器)和ApplicationListener(监听器),都是借助于SpringFactoriesLoder的方式完成初始化。
SpringFactoriesLoader会读取META-INF/spring.factories文件中的配置。一个工程项目中可以同时有多个META-INF/spring.factories文件(每个jar中都能有一个)。
例如在spring-boot-autoconfigure和spring-boot两个jar的META-INF/spring.factories文件中,均有针对ApplicationContextInitializer的配置:
-
推断应用入口类,通过遍历异常堆栈找到方法名称是main的类,将其作为主类
run方法
https://blog.youkuaiyun.com/woshilijiuyi/article/details/82350057
public ConfigurableApplicationContext run(String... args) {
// 统计时间用的工具类,用于记录当前springboot应用启动的开始时间,并且判断当前任务名是否存在,用于保证springboot应用不重复启动
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//设置系统属性,java。awt.headless的值,默认为true,(没有图形化界面),目的是保证即使没有检测到显示器(服务器不需要显示器),也允许程序启动
configureHeadlessProperty();
//获取并启动监听器
//创建所有spring运行监听器并发处开始执行事件
// 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载
// META-INF/spring.factories文件下的类
//通过SpringFactoriesLoader的机制加载所有的SpringApplicationRunListener。从名字就可以看出, SpringApplicationRunListener的作用就是监听SpringApplication.run方法的各个执行阶段,也可以理解成SpringApplication运行的生命周期。加载完所有的SpringApplicationRunListener后,会将其包装在SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 首先调用SpringApplicationRunListener的starting方法
//listeners.starting()这句代码通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用要开始启动啦”。
listeners.starting();
try {
// 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//构造容器环境
//根据springApplicationRunListeners和应用参数来准备spring环境
//prepareEnvironment的作用:加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;初始化日志系统。
//创建环境完成后回调SpringApplicationRunListener.environmentPrepared()方法,表示环境准备完成
//listeners.environmentPrepared(environment)代码再次通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用使用的Environment准备好啦”。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 启动时打印banner
Banner printedBanner = printBanner(environment);
//创建容器
// 创建上下文对象,创建IOC容器:ApplicationContext;决定创建web IOC还是普通的IOC容器
//根据当前应用的类型webApplicationType来匹配对应的ApplicationContext,是servlet、reactive或者非web应用
//创建web应用上下文,对其部分属性:reader、scanner、beanFactory进行了实例化;reader中实例化了属性conditionEvaluator;scanner中添加了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean;beanFactory中注册了8个注解配置处理器
context = createApplicationContext();
//用来支持报告关于启动的错误
// 获取SpringBootExceptionReporter接口的类,异常报告
//这一步的逻辑和实例化初始化器和监听器的一样,都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。ConfigurableApplicationContext
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备容器:加载环境,加载自动装配的bean定义,回调监听器:springboot应用要使用到的bean都已经加载进application了
//Spring上下文前置处理
//将启动类注入容器,为后续开启自动化配置奠定基础
//prepareContext类里面有一个load方法(最终会执行BeanDefinitionLoader的load):load将bean(springApplication)包装成BeanDefinition,参数即为我们项目启动时传递的参数:SpringApplication.run(SpringBootApplication.class, args);由于我们指定了启动类,所以上面也就是加载启动类到容器。,此时只再是将SpringApplication(一个bean)包装成BeanDefinition,count为1
/*
* 准备上下文环境,将environment保存到IOC容器中; 而且applyInitializers(),
* 回调之前保存的所有的applicationContextInitializer的initialize()方法,完成ApplicationContext的初始化;
* 回调所有的SpringApplicationRunListener的contextPrepareded(),通知所有的SpringApplicationRunListener,springboot应用使用的ApplicationContext准备好了
*向ApplicationContext中加载所有的bean,bean的定义可以来自多个source,每个source既可以是XML文件形式的bean配置,也可以Java注解形式的bean配置。在加载bean的过程中,如果配置了@EnableAutoConfiguration注解的话将会识别哪些是需要自动装配。加载bean定义的代码是load方法;
* 最后回调所有的SpringApplicationRunListener的contextLoaded()方法,通知所有的SpringApplicationRunListener,springboot应用要使用到的bean都已经加载进Application拉
*/
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器:启动spring容器,bean对象的创建,开始自动装配
// 核心方法,启动spring容器,刷新Spring上下文,里面有refresh方法,自动配置
//@Configuration、AutoConfigurationImportSelector、AutoConfigurationPackage.Registrar
//Registrar:这个注解的作用就是将主配置类所在的包作为自动配置包进行管理,为后期SpringBoot加载资源提供一个扫描的包路径
//AutoConfigurationImportSelector:用来筛选被@Import的Configuration类,selectImports()加载factories中的全路径,并进行筛选,删选的是xxxAutoConfiguration
//doProcessConfigurationClass:扫描@Component等bean对象
/*
刷新容器:IOC容器的初始化(扫描所有的配置类、@Bean等,加载并创建IOC容器中所有的组件。如果是web应用,
还会创建嵌入式的tomcat),当执行完refreshContext()后,IOC容器即创建完毕
上一个方法是知道哪些需要自动装配,现在这个方法是开始自动装配
*/
refreshContext(context);
//Spring上下文后置处理,是预留的,可根据需求做一些定制化操作
/*
* 从IOC容器中获取所有的ApplicationRunner和CommandLineRunner,
* 然后先回调ApplicationRunner 再回调 CommandLineRunner
*/
afterRefresh(context, applicationArguments);
// 统计结束,停止计时监控类,统计一些任务执行虚拟系
stopWatch.stop();
//输出日志记录执行主类名,事件信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// listeners.started(context)通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用的ApplicationContext已经启动啦”。
listeners.started(context);
// ApplicationRunner
// CommandLineRunner
// 获取这两个接口的实现类,并调用其run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// listeners.running(context)通知所有的SpringApplicationRunListener:“注意啦、注意啦,SpringBoot应用已经开始运行啦”。
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}