Spring Boot
SpringBoot是Spring框架对“
约定优先于配置(Convention Over Configuration)
”理念的最佳实践的产物
可在官网 完成初始化
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication背后的秘密
@SpringBootApplication是一个“三体”结构,实际上它是一个复合Annotation,使用了多个Annotation进行元信息标注,但实际上对于SpringBoot应用来说,重要的只有三个Annotation,而“三体”结构实际上指的就是这三个Annotation:
·@Configuration
·@EnableAutoConfiguration
·@ComponentScan
很多SpringBoot的代码示例都喜欢在启动类上直接标注@Configuration或者@SpringBootApplication
,对于初接触SpringBoot的开发者来说,其实这种做法不便于理解,如果我们将上面的SpringBoot启动类拆分为两个独立的Java类,整个形势就明朗了:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {
@Bean
public Controller controller() {
return new Controller();
}
}
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoConfiguration.class, args);
}
}
所以,启动类DemoApplication其实就是一个标准的Standalone类型Java程序的main函数启动类
,没有什么特殊的。
而@Configuration标注的DemoConfiguration定义其实也是一个普通的JavaConfig形式的IoC容器配置类,没啥新东西,全是Spring框架里的概念!
@EnableAutoConfiguration的功效
简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义:
·@EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
·@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!
@EnableAutoConfiguration作为一个复合Annotation, 其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot.
借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以“智能”地自动配置功效才得以大功告成!
可有可无的@ComponentScan
原则上来说,作为Spring框架里的“老一辈革命家”,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中
。加载bean定义到Spring的IoC容器,我们可以手工单个注册,不一定非要通过批量的自动扫描完成,所以说@ComponentScan是可有可无的。
对于SpringBoot应用来说,同样如此,比如我们本章的启动类:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
如果我们当前应用没有任何bean定义需要通过@ComponentScan加载到当前SpringBoot应用对应使用的IoC容器,那么,除去@ComponentScan的声明,当前SpringBoot应用依然可以照常运行,功能对等!
SpringApplication:SpringBoot程序启动的一站式解决方案
SpringApplication将一个典型的Spring应用启动的流程“模板化”,在没有特殊需求的情况下,默认模板化后的执行流程就可以满足需求了;但有特殊需求也没关系,SpringApplication在合适的流程结点开放了一系列不同类型的扩展点,我们可以通过这些扩展点对SpringBoot程序的启动和关闭过程进行扩展。
最“肤浅”的扩展或者配置是SpringApplication通过一系列设置方法(setters)开放的定制方式,比如,我们之前的启动类的main方法中只有一句:
SpringApplication.run(DemoApplication.class,args);
但如果我们想通过SpringApplication的一系列设置方法来扩展启动行为,则可以用如下方式进行:
public class DemoApplication {
public static void main(String[] args) {
// SpringApplication.run(DemoConfiguration.class, args);
SpringApplication bootstrap = new SpringApplication(Demo-Configuration.class);
bootstrap.setBanner(new Banner() {
@Override
public void printBanner(Environment environment, Class<?> aClass, PrintStream printStream) {
// 比如打印一个我们喜欢的ASCII Arts字符画
}
});
bootstrap.setBannerMode(Banner.Mode.CONSOLE);
// 其他定制设置...
bootstrap.run(args);
}
}
SpringApplication的run方法的实现
主要流程:
1)如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先需要创建一个SpringApplication对象实例
,然后调用这个创建好的SpringApplication的实例run方法。在SpringApplication实例初始化的时候,它会提前做几件事情:
·根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用
使用的ApplicationContext类型,还是应该创建一个标准Standalone应用
使用的ApplicationContext类型。
·使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
·使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
·推断并设置main方法的定义类。
2)SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行开始,
首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener,
调用它们的started()
方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
3)创建并配置当前SpringBoot应用将要使用的Environment
(包括配置要使用的PropertySource以及Profile)。
4)遍历调用所有SpringApplicationRunListener的environmentPrepared()
的方法,告诉它们:“当前SpringBoot应用使用的Environment准备好咯!”。
5)如果SpringApplication的showBanner
属性被设置为true,则打印banner(SpringBoot 1.3.x版本,这里应该是基于Banner.Mode决定banner的打印行为)。这一步的逻辑其实可以不关心,我认为唯一的用途就是“好玩”(Just For Fun)。
6)根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建
什么类型的ApplicationContext
并创建完成,然后根据条件决定是否添加ShutdownHook
,决定是否使用自定义的BeanNameGenerator
,决定是否使用自定义的ResourceLoader
,当然,最重要的,将之前准备好的Environment
设置给创建好的ApplicationContext使用。
7)ApplicationContext创建好之后,SpringApplication会再次借助SpringFactoriesLoader
,查找并加载
classpath中所有可用的ApplicationContextInitializer,然后遍历调用这些ApplicationContextInitializer的 initialize(applicationContext)
方法来对已经创建好的ApplicationContext进行进一步的处理。
8)遍历调用所有SpringApplicationRunListener的contextPrepared()
方法,通知它们:“SpringBoot应用使用的ApplicationContext准备好啦!”
9)最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到
已经准备完毕的ApplicationContext。
10)遍历调用所有SpringApplicationRunListener的contextLoaded()
方法,告知所有SpringApplicationRunListener,ApplicationContext"装填完毕"!
11)调用ApplicationContext的refresh()
方法,完成IoC容器可用的最后一道工序。
12)查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。
13)正常情况下,遍历执行SpringApplicationRunListener的finished()
方法,告知它们:“搞定!”。(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)。
至此,一个完整的SpringBoot应用启动完毕!
整个过程看起来冗长无比,但其实很多都是一些事件通知的扩展点
,如果我们将这些逻辑暂时忽略,那么,其实整个SpringBoot应用启动的逻辑就可以压缩到极其精简的几步,
条件的配置能力或者调整加载顺序
@EnableAutoConfiguration的自动配置功能拥有更加强大的调控能力,通过配合比如基于条件的配置能力或者调整加载顺序,我们可以对自动配置进行更加细粒度的调整和控制。
基于条件的自动配置
基于条件的自动配置来源于Spring框架中“基于条件的配置”这一特性。在Spring框架中,我们可以使用@Conditional
这个Annotation配合@Configuration或者@Bean等Annotation来干预一个配置或者bean定义是否能够生效,其最终实现的效果或者语义类似于如下伪代码:
if(符合@Conditional规定的条件){
加载当前配置(enable current Configuration)或者注册当前bean定义;
}
要实现基于条件的配置,我们只要通过@Conditional指定自己的Condition实现类就可以了(可以应用于类型Type的标注或者方法Method的标注):
@Conditional({MyCondition1.class, MyCondition2.class, ...})
最主要的是,@Conditional可以作为一个Meta Annotation用来标注其他Annotation实现类,从而构建各色的复合Annotation,比如SpringBoot的autoconfigure模块就基于这一优良的革命传统,实现了一批这样的Annotation(位于org.springframework.boot.autoconfigure.condition包下):
·@ConditionalOnClass
·@ConditionalOnBean
·@ConditionalOnMissingClass
·@ConditionalOnMissingBean
·@ConditionalOnProperty
·……
有了这些复合Annotation的配合,我们就可以结合@EnableAuto-Configurationn实现基于条件的自动配置了。
SpringBoot能够风靡,很大一部分功劳需要归功于它预先提供的一系列自动配置的依赖模块,而这些依赖模块都是基于以上@Conditional复合Annotation实现的,这也意味着所有的这些依赖模块都是按需加载的,只有符合某些特定条件,这些依赖模块才会生效,这也就是我们所谓的“智能”自动配置。
调整自动配置的顺序
在实现自动配置的过程中,除了可以提供基于条件的配置,我们还可以对当前要提供的配置或者组件的加载顺序进行相应调整,从而让这些配置或者组件之间的依赖分析和组装可以顺利完成。
我们可以使用@org.springframework.boot.autoconfigure.AutoConfigureBefore或者@org.springframework.boot.autoconfigure.AutoConfigureAfter让当前配置或者组件在某个其他组件之前或者之后进行,比如,假设我们希望某些JMX操作相关的bean定义在MBeanServer配置完成之后进行,那么我们就可以提供一个类似如下的配置:
@Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class)
public class AfterMBeanServerReadyConfiguration {
@Autowired
MBeanServer mBeanServer;
// 通过@Bean添加必要的bean定义
}