文章目录
SpringBoot 自动装配了什么东西
自动装配了一系列被 @Configration 修饰的自动配置类的 Bean 定义到BeanDefinitionMap中,这时还未实例化。
SpringBoot 面试自动装配原理
通过@EnableAutoConfiguration
导入AutoConfigurationImportSelector
类。
在AutoConfigurationImportSelector
中读取META-INF/spring.factories
配置文件中的所有自动配置类的类全路径名,然后批量导入。都是以xxxAutoConfiguration
结尾,每个配置都会配上xxxProperties
作为配置属性。
spring.factories
配置了上百个配置类,现实中不可能会全部引入。
所以每个配置类,都是按条件注解 ConditionalOnClass
进行自动注入的。
被加载到容器中的自动配置类中都装载了需要的 Bean
。
SpringBoot 的配置文件
Spring Boot有一个全局配置文件:application.properties
或application.yml
。
各种属性都可以在这个文件中进行配置,最常配置的比如:server.port
、logging.level.*
等等,实际用到的往往只是很少的一部分,这些属性都可以在官方文档中查找到:
https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#common-application-properties
SpringBoot 自动装配原理
Starter
组件: 提供开箱即用的组件,也叫场景启动器。
什么叫自动装配:自动将 Bean 装配到 IoC 容器中。
SpringBoot 关于自动装配的源码在spring-boot-autoconfigure-x.x.x.x.jar
中。
SpringBoot的启动类上有一个@SpringBootApplication
注解,这个注解是SpringBoot项目必不可少的注解。
自动装配原理就是这个注解来实现的。
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
:SpringBoot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类。@EnableAutoConfiguration
:SpringBoot的精华所在,开启自动配置类。@ComponentScan
:包扫描
@EnableAutoConfiguration注解解剖
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage
:自动配置包,作用是把使用了该注解的类所在的包及子包下所有的组件扫描到IoC容器中。@Import
:给Spring容器中来导入一些组件,并且由AutoConfigurationImportSelector
类来导入。
AutoConfigurationImportSelector类解析:
AutoConfigurationImportSelector
实现了 ImportSelector
,它只有一个 selectImports
抽象方法,并且返回一个 String 数组,这个数组中指定了需要装配到IoC容器的类名称。
在 @Import
中导入了一个 ImportSelector
的实现类之后,会把该实现类中返回的Class名称
都装载到IoC容器中。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 从 META-INF/spring-autoconfigure-metadata.properties 中加载自动装配的条件元数据,就是只有满足条件的 Bean 才能进行装配
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
// 收集所有符合条件的配置类,完成自动装配。
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
重点分析配置类收集方法: getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 @EnableAutoConfiguration 注解中的属性 exclude、excludeName等。
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有自动装配的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去除重复的配置项
configurations = removeDuplicates(configurations);
// 根据 @EnableAutoConfiguration 注解的 exclude 属性,把不需要自动装配的类移除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
注意: 在 AutoConfigurationImportSelector
中不执行 selectImports
方法,而是通过 ConfigurationClassPostProcessor
中的 processConfigBeanDefinitions
方法来扫描和注册所有配置类的 Bean, 最终还是会调用 getAutoConfigurationEntry
方法获得所有需要自动装配的配置类。
分析 getCandidateConfigurations 方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
SpringFactoriesLoader
扫描 classpath
下的META-INF/spring.factories
文件。
spring-boot-autoconfigure-x.x.x.x.jar
里就有一个这样的spring.factories
文件。
这个spring.factories
文件是key=value的形式,其中一个key是EnableAutoConfiguration
类的全类名,而它的value是一个xxxxAutoConfiguration
的类名的列表,这些类名以逗号分隔,每一个xxxAutoConfiguration类都是容器中的一个组件,并都加入到容器中。如下图所示:
总结:
这个@EnableAutoConfiguration
注解通过@SpringBootApplication
被间接的标记在了SpringBoot的启动类上。在SpringApplication.run(...)
的内部就会执行selectImports()
方法,找到所有JavaConfig
自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
自动配置生效
每一个xxxxAutoConfiguration
自动配置类都是在某些条件之下才会生效的,这些条件的限制在SpringBoot中以注解的形式体现,常见的条件注解有如下几项:
- @ConditionalOnBean:当容器里有指定的bean的条件下。
- @ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
- @ConditionalOnClass:当类路径下有指定类的条件下。
- @ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
- @ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
以ServletWebServerFactoryAutoConfiguration
配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081
,是如何生效的。
在ServletWebServerFactoryAutoConfiguration
类上,有一个@EnableConfigurationProperties
注解:开启配置属性,而它后面的参数是一个ServerProperties
类,这就是习惯优于配置的最终落地点。
在这个类上有一个@ConfigurationProperties
注解,它的作用就是从配置文件中绑定属性到对应的bean
上,而@EnableConfigurationProperties
负责将bean
加载到spring
容器中。那么这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties
类,它与配置文件中定义的prefix
关键字开头的一组属性是唯一对应的。
总结:
全局配置的属性如:server.port
等,通过@ConfigurationProperties
注解,绑定到对应的XxxxProperties
配置实体类上封装为一个bean
,然后再通过@EnableConfigurationProperties
注解导入到Spring
容器中。
而其他的XxxxAutoConfiguration
自动配置类,就是Spring
容器的JavaConfig
形式,作用就是为Spring
容器导入bean
,而所有导入的bean
所需要的属性都通过xxxxProperties
的bean
来获得。
再举一个例子:
RedisAutoConfiguration
会生成RedisTemplate
对象并加载到了Spring
容器,程序中就可以注入这个对象了。
RestTemplateAutoConfiguration 源码解析
@Configuration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
除了基本的 @Configuration
注解,还有 @ConditionalOnClass
注解。
@ConditionalOnClass
注解意思是判断 calsspath
下是否存在 RabbitTemplate
、Channel
类,如果是,把当前配置类注册到 IoC 容器。
@EnableConfigurationProperties
属性配置,按照约定在 application.properties
中配置 RabbitMQ 参数,这些配置会加载到 RabbitProperties
中。
自动配置原理总结
SpringBoot启动的时候会通过@EnableAutoConfiguration
注解找到META-INF/spring.factories
配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration
结尾来命名的,它实际上就是一个JavaConfig
形式的Spring
容器配置类,它能通过以Properties
结尾命名的类中取得在全局配置文件中配置的属性如:server.port
,而XxxxProperties
类是通过@ConfigurationProperties
注解与全局配置文件中对应的属性进行绑定的。
XxxxProperties
类的含义是:封装配置文件中相关属性;
XxxxAutoConfiguration
类的含义是:自动配置类,目的是给容器中添加组件。
@EnableAutoConfiguration
开启自动配置类。- 通过
@Import(AutoConfigurationImportSelector)
实现配置类的导入。 AutoConfigurationImportSelector
类实现了ImportSelector
接口,重写了方法selectImports
,用于批量配置类的装配。- 扫描
classpath
路径下的META-INF/spring.factories
文件,读取需要实现自动装配的配置类。
自定义starter
SpringBoot为我们提供了自动化装配的功能,简单方便。可以像使用插件一样,对各个组件自由组合装配。只需引入定义好的 starter 即可。starter方式实现了模块化完全解耦,实现热插拔功能。
自定义一个自动化装配的实现,自定义starter。
第一步:
定义一个配置类模块:
import com.demo.entity.SimpleBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
public class MyAutoConfiguration {
static {
System.out.println("myAutoConfiguration init...");
}
@Bean
public SimpleBean simpleBean(){
return new SimpleBean();
}
}
第二步:
新建一个项目作为starter模块,里面无需任何代码,pom也无需任何依赖,只需在META-INF下面建一个 spring.factories文件,添加如下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.study.startup.bean.MyAutoConfiguration
第三歩:
在启动类项目中引入我们的 starter 模块即可。
<dependency>
<groupId>com.demo</groupId>
<artifactId>springboot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
最终在AutoConfigurationImportSelector
解析spring.factories
文件:
SpringBoot提供了配置类有180多个,现实中不可能会全部引入。所以在自动装配的时候,会去ClassPath下面寻找,是否有对应的配置类。如果有配置类,则按条件注解 @Conditional或者@ConditionalOnProperty等相关注解进行判断,决定是否需要装配。如果ClassPath下面没有对应的字节码,则不进行任何处理。
上面自定义的配置类也是以相同的逻辑进行装配,指定了以下注解:
@ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
默认为 true,所以自定义的starter成功执行。
自定义starter案例2
Starter 的核心就是条件注解 @Conditional ,当 classpath 下存在某一个 Class 时,某个配置才会生效。
1、创建一个普通的Maven项目,并导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
2、创建一个 HelloProperties 类,用来接受 application.properties 中注入的值,并提供默认值
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private static final String DEFAULT_NAME = "James";
private static final String DEFAULT_MSG = "hello world";
private String name = DEFAULT_NAME;
private String msg = DEFAULT_MSG;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
@ConfigurationProperties 表示类型安全的属性注入,即将 application.properties 文件中前缀为 hello 的属性注入到这个类对应的属性上。
application.properties 中的配置文件如下:
hello.name=zhangsan
hello.msg=java
3、创建一个 HelloService 类
public class HelloService {
private String msg;
private String name;
public String sayHello() {
return name + " say " + msg + " !";
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4、创建一个自动配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnClass(HelloService.class)
public class HelloServiceAutoConfiguration {
@Autowired
HelloProperties helloProperties;
@Bean
HelloService helloService() {
HelloService helloService = new HelloService();
helloService.setName(helloProperties.getName());
helloService.setMsg(helloProperties.getMsg());
return helloService;
}
}
自动配置类中首先注入 HelloProperties ,这个实例中含有我们在 application.properties 中配置的相关数据。
提供一个 HelloService 的实例,将 HelloProperties 中的值注入进去。
注解解析:
- @Configuration 注解表明这是一个配置类。
- @EnableConfigurationProperties 注解是使我们之前配置的 @ConfigurationProperties 生效,让配置的属性成功的进入 Bean 中。
- @ConditionalOnClass 表示当项目当前 classpath 下存在 HelloService 时,后面的配置才生效。
5、创建一个 spring.factories 文件
为什么需要 spring.factories 文件?
SpringBoot 项目的启动类都有一个 @SpringBootApplication
注解,这个注解是一个组合注解,其中包含一个 @EnableAutoConfiguration
注解。
@EnableAutoConfiguration
表示启用 Spring 应用程序上下文的自动配置,该注解会自动导入一个名为 AutoConfigurationImportSelector
的类,而这个类会去读取一个名为 spring.factories
的文件,在 spring.factories 中定义了需要加载的自动化配置类,打开任意一个框架的 Starter ,都能看到它有一个 spring.factories 文件,例如 MyBatis 的 Starter 如下:
那么我们就在 Maven 项目的 resources 目录下创建一个名为 META-INF 的文件夹,然后在文件夹中创建一个名为 spring.factories 的文件,文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.starter.HelloServiceAutoConfiguration
在这里指定自动化配置类的路径即可。
6、使用上面自定义的Starter
首先将上面的Maven打包,并引入。
在引入了上面自定义的 Starter 后,现在项目中就有一个默认的 HelloService 实例可以使用了,而且关于这个实例的数据,也可以在 application.properties 中进行配置。
hello.name=Tom
hello.msg=Hadoop
直接在单元测试方法中注入 HelloSerivce 实例来使用。
import com.demo.starter.HelloService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
HelloService helloService;
@Test
public void contextLoads() {
System.out.println(helloService.sayHello());
}
}
模拟自动装配
自定义配置:
rest.max = 50
rest.min = 10
读取自定义配置:
public class RestTemplateProperties {
@Value("${rest.max}")
private Integer max;
@Value("${rest.min}")
private Integer min;
// setter/getter
}
导入组件配置类:
public class UleImportSelector implements ImportSelector {
private Class<?> getSpringFactoriesLoaderFactoryClass() {
return UleEnableAutoConfig.class;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> configurations = getCandidateConfigurations();
System.out.println(configurations);
return StringUtils.toStringArray(configurations);
}
protected List<String> getCandidateConfigurations() {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
UleImportSelector.class.getClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
}
通过 @Import 导入组件:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UleImportSelector.class)
public @interface UleEnableAutoConfig {
}
配置类:
@Configurable
@PropertySource(value = {"classpath:restTemplate.properties"})
public class RestTemplateConfig {
@Bean
public RestTemplateProperties jdbcProperties(){
RestTemplateProperties restTemplateProperties = new RestTemplateProperties();
return restTemplateProperties;
}
@Bean
public RestTemplate restTemplate(@Autowired RestTemplateProperties restTemplateProperties) {
System.out.println(restTemplateProperties.toString());
return new RestTemplate();
}
}
导入组件配置类读取的类全路径名:
com.example.config.UleEnableAutoConfig=\
com.example.config.RestTemplateConfig
@Configurable
@UleEnableAutoConfig
public class SpringConfig {
}
加载 SpringConfig 配置类就可以获取 RestTemplate类了:
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Object bean = context.getBean("restTemplate");
RestTemplateProperties restTemplateProperties = context.getBean(RestTemplateProperties.class);
System.out.println(restTemplateProperties.toString());
System.out.println(bean);
}
}
SpringBoot启动原理
创建 SpringApplication
1、进入 SpringApplication.run 方法
2、new SpringApplication(primarySources):创建 SpringApplication
2.1、判断当前应用环境
- Reactive 环境(全NIO),有 WebFlux 的类但没有 WebMvc 的类
- Servlet(WebMvc) 环境
- 非Web环境,没有有跟 Servlet 相关的类
3.2、 setInitializers:设置初始化器
- 将一组类型为 ApplicationContextInitializer 的初始化器放入 SpringApplication 中。
- 在容器刷新之前调用 ApplicationContextInitializer 实现类的 initialize 方法,并将 ConfigurableApplicationContext 类的实例传递给该方法。
- SpringFactoriesLoader.loadFactoryNames 从 spring.factories 文件加载
3.3、 setListeners:设置监听器
- ApplicationListener 实现类
总结:
1、SpringApplication 的创建和运行是两个不同的步骤。
2、SpringBoot 会根据当前classpath下的类来决定Web应用类型。
3、SpringBoot 的应用中包含两个关键组件:ApplicationContextInitializer 和 ApplicationListener ,分别是初始化器和监听器,它们都在构建 SpringApplication 时注册。
run():启动SpringApplication
public ConfigurableApplicationContext run(String... args) {
// 4.1 创建StopWatch对象
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 4.2 创建空的IOC容器,和一组异常报告器
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 4.3 配置与awt相关的信息
configureHeadlessProperty();
// 4.4 获取SpringApplicationRunListeners,并调用starting方法(回调机制)
SpringApplicationRunListeners listeners = getRunListeners(args);
// 【回调】首次启动run方法时立即调用。可用于非常早期的初始化(准备运行时环境之前)。
listeners.starting();
try {
// 将main方法的args参数封装到一个对象中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 4.5 准备运行时环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//.............
}
1、 创建StopWatch对象
2、 创建空的IOC容器,和一组异常报告器
3、 配置与awt相关的信息
4、 获取SpringApplicationRunListeners,并调用starting方法(回调机制)
5、 准备运行环境
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
总结:
1、获取启动类
2、获取 web 应用类型
3、读取了对外扩展的 ApplicationContextInitializer,ApplicationListener
4、根据 main 推算出所在的类
就是初始化了一些信息。
public ConfigurableApplicationContext run(String... args) {
// 用来记录 SpringBoot 启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(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;
}