SpringBoot源码研究之Start

来源:互联网 发布:电脑网络适配怎么修复 编辑:程序博客网 时间:2024/05/29 16:57

虽然现在公司还是在使用SSM, 也看完了相关的源码. 不过看着SpringBoot越来越火, 于是抽了一点时间看了下相关的源代码.

本人阅读的版本是 spring-boot-1.5.7.RELEASE.jar

另外强烈建议理解了Spring源码研究之@Configuration之后再阅读本文.

1. 入口

SpringBoot程序的入口就是这么简单, 只需要如下这么几行代码, 就能将整个程序有条不紊地运行起来, 不需要任何其他的显式配置(当然前提是你需要遵从它的约定).

@SpringBootApplicationpublic class FirstApplication {    public static void main(String[] args) {        // 最终执行的是 :        // new SpringApplication(sources).run(args)        SpringApplication.run(FirstApplication.class, args);    }}

一眼看上去, 就知道关键点有两个:
1. SpringApplication
2. @SpringBootApplication注解

2. SpringApplication

2.1 构造函数

最终是调用了如下方法

private void initialize(Object[] sources) {    // sources 正是我们传入 [FirstApplication.class]    if (sources != null && sources.length > 0) {        this.sources.addAll(Arrays.asList(sources));    }    // webEnvironment为boolean类型, 这里是通过尝试加载Java Web环境下的必备类(由中SpringApplication类的私有字段WEB_ENVIRONMENT_CLASSES所存储,这些类必须都能被正确加载)来判断当前是否为Web环境    this.webEnvironment = deduceWebEnvironment();    /* 以下代码会对classpath下所有jar包中META-INF/spring.factories文件里所声明的类进行扫描筛选加载,筛选结果加载到全局变量this.initializers中.        例如这里就会加载类似这样的类(摘抄自spring-boot-1.5.7.RELEASE.jar的META-INF/spring.factories文件):# Application Context Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\org.springframework.boot.context.ContextIdApplicationContextInitializer,\org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer   */    setInitializers((Collection) getSpringFactoriesInstances(            ApplicationContextInitializer.class));    // 同上, 不过这次是全局变量this.listeners    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    // 推测当前的main类    // 这里有个技巧是通过主动构建一个RuntimeException实例来获取线程的堆栈信息, 值得借鉴.    this.mainApplicationClass = deduceMainApplicationClass();}

2.2 run方法

实例方法run(String... args), 启动Application

/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */public ConfigurableApplicationContext run(String... args) {    // 准备开始统计程序启动的耗时.    StopWatch stopWatch = new StopWatch();    stopWatch.start();    ConfigurableApplicationContext context = null;    FailureAnalyzers analyzers = null;    // headless模式    configureHeadlessProperty();    // 加载 ApplicationContextInitializer 接口实现类    // 这里有技巧是使用package访问级别的SpringApplicationRunListeners来封装对检索出来的SpringApplicationRunListener集合的统一操作.    // 加载的规则是 classpath下所有jar中META-INF/spring.factories文件里所声明的类进行筛选    /* --------- 以下摘抄自spring-boot-1.5.7.RELEASE.jar的META-INF/spring.factories文件        # Run Listeners    org.springframework.boot.SpringApplicationRunListener=\    org.springframework.boot.context.event.EventPublishingRunListener    */    SpringApplicationRunListeners listeners = getRunListeners(args);    // 迭代回调每个SpringApplicationRunListener    listeners.starting();    try {        // 委托其他类去解析用户通过命令行传入的参数        ApplicationArguments applicationArguments = new DefaultApplicationArguments(                args);        // 会迭代回调每个SpringApplicationRunListener的environmentPrepared方法        ConfigurableEnvironment environment = prepareEnvironment(listeners,                applicationArguments);        // 默认的打印内容存在于 `SpringBootBanner`类中.        // 选择往哪个位置打印Banner(横幅;标语;)        Banner printedBanner = printBanner(environment);        // 1. 下面这行代码会实例化AnnotationConfigEmbeddedWebApplicationContext类, 进而实例化其内部的AnnotatedBeanDefinitionReader类型的reader字段,        //  而AnnotatedBeanDefinitionReader类型的reader字段实例化时会调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);        //  这样就确保了处理@Configuration、@Autowired、@Value、@Inject等相关的处理器被加载到容器中        context = createApplicationContext();        analyzers = new FailureAnalyzers(context);        // 2. 下面这行代码会将我们传入的诸如被@SpringBootApplication所修饰的FirstApplication注册到容器中.        // 也就是其中的这行代码 : load(context, sources.toArray(new Object[sources.size()])); // 注意这里的Set转换为Array的方式        prepareContext(context, environment, listeners, applicationArguments,                printedBanner);        // 3. 下面这行代码调用AbstractApplicationContext类中定义的refresh(); 这就导致容器中所有的BeanFactoryPostProcessor接口被回调        //  而处理@Configuration的ConfigurationClassPostProcessor类正是实现了BeanFactoryPostProcessor接口        //----- 所以以上所标注的1,2,3的先后顺序就对上了, @Configuration等将被正确解析, 这就接上了前一篇博客《Spring源码研究之@Configuration》        refreshContext(context);        // refresh完毕之后, 调用所有的Runner        afterRefresh(context, applicationArguments);        listeners.finished(context, null);        // 停止计时        stopWatch.stop();        // 记录启动相关的信息        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)                    .logStarted(getApplicationLog(), stopWatch);        }        return context;    }    catch (Throwable ex) {        // 处理异常情况        handleRunFailure(context, listeners, analyzers, ex);        throw new IllegalStateException(ex);    }}

3. @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 {    //略}// --------------- 而@SpringBootConfiguration@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented // 以上三个忽略@Configurationpublic @interface SpringBootConfiguration {    // 空}
  1. 因为有了@SpringBootConfiguration上的@Configuration, 这样@ComponentScan就会被处理, 处理的位置参见 ConfigurationClassParser类的doProcessConfigurationClass (参见博客Spring源码研究之@Configuration的第七小节).
  2. @ComponentScan 的处理是由ComponentScanAnnotationParser来完成的. 其中的细节之一就是如果@ComponentScan中的basePackages没有进行赋值的话, 则会将所传入的类所在的package作为basepackage, 这也解释了为什么我们经常被提醒要将被@SpringBootApplication注解的类(在这里就是FirstApplication)放在根package下.
  3. 也正是因为有了@SpringBootConfiguration上的@Configuration, 所以我们在我们的FirstApplication类中直接进行如下操作是完全可以的!
// 向容器中注册一个Map<String,Object>类型的名为myBean的Bean@Beanpublic Map<String,Object> myBean(){     HashMap<String,Object> hashMap = new HashMap<String,Object>();     hashMap.put("1", 1);     hashMap.put("2", 2);     return hashMap;}

4. 闪光点

4.1 TypeExcludeFilter

  1. 很有意思的一个类.
  2. 观察其对接口TypeFilter的实现, 它所做的只是从现有的容器中收集TypeExcludeFilter 类或者TypeExcludeFilter 子类的实例的集合. 然后对该集合作一个迭代调用.
  3. 所以当我们有自定义的过滤需求时, 可以直接编写一个继承自TypeExcludeFilter的类, 然后注册到容器中. 剩下的事情Spring就帮我们代劳了.

4.2 与Spring Java配置的相似之处

  1. 首先看看使用@Configuration进行配置时, 相关的容器类AnnotationConfigApplicationContext中的结构
    AnnotationConfigApplicationContext中的字段

  2. 我们再来看看SpringBoot中的默认容器AnnotationConfigEmbeddedWebApplicationContext
    AnnotationConfigEmbeddedWebApplicationContext中的字段

是不是非常相似!而且为了更突出两者的相似之处, 我特意多截取了一个构造函数.

另外其中的字段AnnotatedBeanDefinitionReader类实例化时会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry) , 这会向容器中注册一系列处理针对@Configuration@Autowired@Value@Inject等相关的处理器(更多细节参见本人另外一篇博客Spring源码研究之@Configuration 第五小节)

5. 总结

  1. 我们的FirstApplicationmain方法里那行代码的执行会准备好ApplicationContext容器, 注册相应注解的处理器, 以及其他一些准备工作和抽取外部约定的扩展功能并入自身.
  2. 然后借助上一步中注册的注解处理器, 将FirstApplication所在package下所有满足要求的bean注入到容器中.
  3. 整段逻辑看下来, 可以发现: SpringBoot并没有做什么革新, 个人觉得它最大的贡献就是给出一套经过真实环境考验过的约定, CoC原则大大降低了开发人员的学习曲线, 从而极大提升了开发效率.
  1. http://blog.csdn.net/liaokailin/article/details/49107209 – 有解析各个常见注解的代码讲解
  2. http://blog.csdn.net/hengyunabc/article/details/50120001 –有关于SpringBoot打包而成的jar结构等的讲解, 非常棒!
  3. http://www.cnblogs.com/zheting/p/6707035.html
原创粉丝点击