SpringBoot启动流程(七):最后的处理

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设计的精巧是在令人叹服,在阅读代码中难免会有疏漏和错误,如果各位读者
发现请立即指正,我会立即更正,以免误导他人,谢谢大家.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值