SpringBoot启动流程(七)
之前我们看了SpringBoot的Context初始化和加载,下面继续研究启动流程:
前略:
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
接下来调用的是afterRefresh这个方法是个空方法,然后走到下面listeners.started(context);处理各个listener,最后callRunners
这里面重要的就是callRunners,一开始我看这块儿的源码就是因为我的实现CommandLineRunner的类走了两遍,后来发现是有别的类继承了
实现CommandLineRunner 的类导致的.然后才开始研究Springboot的启动流程.
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
从beanFactory中获取ApplicationRunner和CommandLineRunner修饰的类的对象,并加入runners中.然后在排序runners,遍历
runners之后如果是ApplicationRunner则走上面,否则走下面.
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
直接调用重写的run方法即可实现.
处理完runners后调用listeners.running处理,并返回context.启动流程就完成了.
我们来最后总结下启动时到底做了些什么?
1.首先创建SpringApplication类的对象,初始化SpringApplication,当初始化的时候从spring.factory中获取所有的监听器,
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)),获取所有的Initializer,然后获取
启动类.然后调用run方法处理.
2.在run方法里,最重要的事儿是初始化了两个重要的对象:Environment和Context,一个加载了Spring的环境(配置文件中的内容),
另一个存放了后面用到的对象,在Context中同样有个重要的对象:beanFactory,存放了我们用注解修饰的类的对象,在后面直接从
beanFactory中获取就可以了.具体一点儿来说调用prepareEnvironment方法先来创建environment对象(根据webApplicationType(在初始化
Application对象的啥时候有赋值),通过getOrCreateEnvironment获取对象),然后把启动jar包时传入的以--开头的与Spring有关的参数
放入environment中例如--Spring.profiles.active=XXX,为之后获取配置文件中的内容做准备,然后调用listeners.environmentPrepared(environment);
读取配置文件(在 ConfigFileApplicationListener 中获取),在进行绑定操作.Context的流程类似,也是通过prepareContext来创建Context对象的,
所不同的是它还调用了refreshContext方法来加载BeanFactory中的对象(如实现CommandLineRunner的类,@@Component修饰的类),具体来看首先加载
启动类到Context中的BeanFactory,因为启动类被@SpringBootApplication注解类:注解修饰,所以会被parser.parse扫描,
SpringBootApplication注解类:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
这个类同样有很多注解,重要的是@ComponentScan 注解,在parser.parse中会递归的遍历被ComponentScan修饰的类,然后
获取里面的内容,在把内容进行扫描在继续处理.在我们没有写@ComponentScan 注解时,Spring会自动的读取启动类的包名,
并把这个包名当做basePackages从而扫描,注意:当同事有@ComponentScan和启动类时,这两个扫描会同时进行,即会扫描
@ComponentScan 中指明的basePackages和启动类的包名的下的类.获取完之后返回Context,SpringBoot的启动流程就完成了!
结语:这次是我第一次真正意义上独立学习源码知识,Spring设计的精巧是在令人叹服,在阅读代码中难免会有疏漏和错误,如果各位读者
发现请立即指正,我会立即更正,以免误导他人,谢谢大家.