SpringBoot自动配置原理,以及如何编写自定义的starter
简介
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、RabbitMQ等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
前置知识
批量注册bean的方式
- 通过@Bean标注方法的方式,一个个来注册
- @CompontentScan的方式:默认的@CompontentScan是无能为力的,默认情况下只会注册@Compontent标注的类
- @Import
前两种方式,不适用于频繁变化的场景,且有局限性,所以SpringBoot作为脚手架,自动装配第三方类显然第三种方式更适合;
因此我们首先来了解下@Import注解;
@Import注解
作用:@Import可以用来批量导入需要注册的各种类,如普通的类、配置类,然后完成普通类和配置类中所有bean的注册。
支持如下三种方式:
1、直接导入普通类
- 创建一个普通类
package com.doudou.imports;
public class DouDouBean {
public String print() {
return "return doudou bean";
}
}
- 创建一个配置类,导入刚创建的类
@Configuration
@Import({DouDouBean.class})
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
DouDouBean douDouBean = context.getBean(DouDouBean.class);
System.out.println(douDouBean.print());
}
}
- 控制台输出结果
2、配合自定义的 ImportSelector 使用
ImportSelector是一个接口,该接口提供了一个selectImports方法,用来返回全类名数组;可以通过这种方式,动态导入N个bean
- 创建普通类
@Configuration
public class DouDouConfig {
@Bean
public String RedBeanBun() {
return "Red Bean Bun";
}
@Bean
public String OatBag() {
return "Oat Bag";
}
}
public class DouDouBean {
public String print() {
return "return doudou bean";
}
}
- 实现ImportSelector接口
public class DouDouImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
DouDouBean.class.getName(),
DouDouConfig.class.getName()
};
}
}
- 创建一个配置类,导入实现了ImportSelector接口的类
@Configuration
@Import({DouDouImportSelector.class})
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s=%s", name, context.getBean(name)));
}
}
}
- 控制台输出结果
2.1 重点:DeferredImportSelector
装个杯,如果让你实现一个框架,比如希望用户如果实现了自己的配置类,跟SpringBoot自动加载的配置类相同,希望以用户的为准,你会怎么做?
SpringBoot实现上述需要就是使用了DeferredImportSelector;
SpringBoot中的核心功能@EnableAutoConfiguration就是靠DeferredImportSelector来实现的;
DeferredImportSelector是ImportSelector的子接口,所以也能够通过@Import导入,跟ImportSelector不同点在于:
- 分组
- 延时导入
- 创建需要延迟导入的配置类
@Configuration
public class DeferredConfig {
@Bean
public String dog() {
return "dog";
}
}
- 实现DeferredImportSelector接口
public class DouDeferredImportSelect implements DeferredImportSelector {
@Override
public Class<? extends Group> getImportGroup() {
return DeferredImportSelector.super.getImportGroup();
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
DeferredConfig.class.getName()
};
}
}
- 创建一个配置类,导入实现了DeferredImportSelector接口的类
@Configuration
@Import({
DouDeferredImportSelect.class,
DouDouImportSelector.class})
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s=%s", name, context.getBean(name)));
}
}
}
- 控制台输出结果,可以发现DeferredConfig最后输出,跟Import导入顺序不一致
3、配合 ImportBeanDefinitionRegistrar 使用
ImportBeanDefinitionRegistrar也是一个接口,支持手动的注册bean到容器当中
- 创建需要被手动注册到bean容器当中的类
public class DouDefinitionRegistrarBean {
public String pig() {
return "pig";
}
}
- 实现ImportBeanDefinitionRegistrar接口
public class DouDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(DouDefinitionRegistrarBean.class);
registry.registerBeanDefinition("douDefinitionRegistrarBean", definition);
}
}
- 创建配置类,导入实现了ImportBeanDefinitionRegistrar接口的类
@Configuration
@Import({
DouDeferredImportSelect.class,
DouDouImportSelector.class,
DouDefinitionRegistrar.class})
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s=%s", name, context.getBean(name)));
}
}
}
- 控制台输出结果,可以发现DouDefinitionRegistrarBean类被注入
@Condition注解
@Condition注解能够实现在满足特定条件下配置类才生效
- 创建一个配置类,实现Condition接口,改接口仅提供了一个matches方法,返回值为boolean类型,该方法返回true,代表配置类生效;反之,不生效;
@Configuration
public class DouDouConditionBean implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
- 创建另一个配置类,依赖上述配置类matches方法来判断是否需要生效
@Configuration
@Conditional({DouDouConditionBean.class})
public class ConditionBeanConfig {
}
- 创建配置类,测试ConditionBeanConfig配置类是否被注入
@Configuration
@ComponentScan(basePackages = {"com.doudou.condition"})
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s=%s", name, context.getBean(name)));
}
}
}
- 控制台结果输出,ConditionBeanConfig类生效,被成功注入到IOC容器
拓展注解
@Conditional扩展注解 | (判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
1、SpringBoot自动配置分析
注意:本篇博文分析,基于SpringBoot 2.5.2版本
自动配置原理流程图
上述识别到需要自动配置的配置类,被生成BeanDefinitionMap,通过BeanFactory生成具体的Bean,该部分内容由Spring IOC完成,非SpringBoot做的事,所以一笔带过,主要让大家别太懵逼;
具体源码分析,先从启动类入手:
@SpringBootApplication注解
该注解标注该类是SpringBoot的主配置类
@Target(ElementType.TYPE) // 设置该注解可以标注在什么地方
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 是否会被继承
@SpringBootConfiguration // 表示这是SpringBoot的主配置类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
由此可见,最核心的注解还是 @EnableAutoConfiguration,展开分析下这个注解都由什么部分组成
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
可以发现,核心的就两个注解 @AutoConfigurationPackage、 @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
首先分析下 @AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
看到 @Import(AutoConfigurationPackages.Registrar.class) 这个东西,是不是很熟悉了,就是帮我们指定哪些bean需要被注册到Spring IOC容器中;
通过调试,我们可以看到,他将我们启动类所在的目录注册到Spring IOC容器中去了,换句话说,如果没有 @AutoConfigurationPackage注解,我们自己项目的类不会自动被扫描到SpringIOC容器中的;所以,总结一下,该注解主要起到了将启动类所在目录下的包扫描到SpringIOC容器中;
@Import(AutoConfigurationImportSelector.class)
再分析下 @Import(AutoConfigurationImportSelector.class)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
该类实现了DeferredImportSelector接口,若getImportGroup()方法返回值不为null,将会调用getImportGroup()方法返回值的process()方法;
具体堆栈如下:
process:429, AutoConfigurationImportSelector$AutoConfigurationGroup (org.springframework.boot.autoconfigure)
getImports:879, ConfigurationClassParser$DeferredImportSelectorGrouping (org.springframework.context.annotation)
processGroupImports:809, ConfigurationClassParser$DeferredImportSelectorGroupingHandler (org.springframework.context.annotation)
process:780, ConfigurationClassParser$DeferredImportSelectorHandler (org.springframework.context.annotation)
parse:193, ConfigurationClassParser (org.springframework.context.annotation)
processConfigBeanDefinitions:331, ConfigurationClassPostProcessor (org.springframework.context.annotation)
postProcessBeanDefinitionRegistry:247, ConfigurationClassPostProcessor (org.springframework.context.annotation)
invokeBeanDefinitionRegistryPostProcessors:311, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:112, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:746, AbstractApplicationContext (org.springframework.context.support)
refresh:564, AbstractApplicationContext (org.springframework.context.support)
refresh:145, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:754, SpringApplication (org.springframework.boot)
refreshContext:434, SpringApplication (org.springframework.boot)
run:338, SpringApplication (org.springframework.boot)
run:1343, SpringApplication (org.springframework.boot)
run:1332, SpringApplication (org.springframework.boot)
main:10, SpringbootApplication (com.doudou)
详细分析下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process方法
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 核心代码:获取需要自动加载的配置类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
}
深度分析下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有jar包 META-INF/spring.factories 下的key为EnableAutoConfiguration的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 配置类去重
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 过滤出满足条件的配置类,过滤规则:META-INF/spring.factories key为AutoConfigurationImportFilter的value值
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
深度分析下核心方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass方法比较简单,就是返回EnableAutoConfiguration.class
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}
分析下org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 这里获取到的内容为EnableAutoConfiguration
String factoryTypeName = factoryType.getName();
// 过滤出key为EnableAutoConfiguration的配置类
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
分析下org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
// 可以看到,通过类加载机制,获取到所有jar包下META-INF/spring.factories的配置信息
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
...
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
上述多次提到key EnableAutoConfiguration、AutoConfigurationImportFilter,你可能没概念,去到具体文件,截图一下,你就明白了;
至此,需要自动加载到Spring IOC的配置类均找到了,接下来交给Spring IOC即可;
2、如何查看哪些配置类被自动加载至Spring容器中
在项目appilication.yml追加以下一条配置项,这样就能够在控制台输出哪些配置类被自动配置;
debug: true
控制台输出结果(内容较多,我拣选了核心的出来):
positive matches: // 以下代表被自动配置的配置类信息
-----------------
AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)
DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:
- @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)
- Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)
Negative matches: // 以下代表条件缺失,没有被自动配置的配置类
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
AopAutoConfiguration.AspectJAutoProxyingConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.weaver.Advice' (OnClassCondition)
ArtemisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
BatchAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.springframework.batch.core.launch.JobLauncher' (OnClassCondition)
Exclusions: // 哪些类被排除
-----------
None
3、单独分析一个满足自动装配条件的类
以HttpEncodingAutoConfiguration类为例
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class) 该注解表示 该配置类对应的配置文件对应的class类,并且将ServerProperties对应的javaBean加入到Spring IOC容器中
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) web环境下生效
@ConditionalOnClass(CharacterEncodingFilter.class) CharacterEncodingFilter类存在,配置类则生效
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
配置中是否存在某个配置项为server.servlet.encoding,如果不存在,则能够匹配;如果matchIfMissing为false,则需要存在才能够匹配;
4、分析下配置项和配置类如何互相绑定
以ServerProperties为例
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
}
ServerProperties是通过@ConfigurationProperties将配置文件与该类进行绑定;
因此你在application.properties或application.yml中给服务绑定端口号时,实际上就给ServerProperties属性赋值;
server:
port: 8080
5、自定义starter
简介
SpringBoot最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们引入SpringBoot为我们提供的这些场景启动器,我们再进行少量的配置就能完成我们的功能。即使是这样,SpringBoot也不能囊括我们所有的场景,往往我们需要自定义starter,来满足我们需要的功能;
如何编写starter
我们参考spring-boot-starter
发现是一个空的jar包,分析下他的pom.xml文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.2</version>
<scope>compile</scope>
</dependency>
发现它依赖了spring-boot-autoconfigure,而spring-boot-autoconfigure包含了starter的配置及代码
因此,梳理出规则如下:
- 启动器(starter)是一个空的jar文件,仅仅提供辅助性依赖管理,这些依赖可能用于自动装配或其他类库。
- 需要专门写一个类似spring-boot-autoconfigure的配置模块
- 用的时候只需要引入启动器starter,就可以使用自动配置了
命名规范
官方命名空间
- 前缀:spring-boot-starter-
- 模式:spring-boot-starter-模块名
- 举例:spring-boot-starter-web、spring-boot-starter-jdbc
自定义命名空间
- 后缀:-spring-boot-starter
- 模式:模块-spring-boot-starter
- 举例:mybatis-spring-boot-starter
开始编写自己的starter
创建一个父工程,和2个Module:
1、doudou-parent (父工程)
2、doudou-spring-boot-starter (子模块,依赖doudou-spring-boot-starter-autoconfigure)
3、doudou-spring-boot-starter-autoconfigure
项目结构概览:
doudou-parent:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.doudou</groupId>
<artifactId>doudou-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>doudou-parent</name>
<description>doudou-parent</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
</dependencies>
<modules>
<module>doudou-spring-boot-starter</module>
<module>doudou-spring-boot-starter-autoconfigure</module>
</modules>
</project>
doudou-spring-boot-starter:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.doudou</groupId>
<artifactId>doudou-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>doudou-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>doudou-spring-boot-starter</name>
<description>doudou-spring-boot-starter</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.doudou</groupId>
<artifactId>doudou-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
doudou-spring-boot-starter-autoconfigure:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.doudou</groupId>
<artifactId>doudou-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>doudou-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>doudou-spring-boot-starter-autoconfigure</name>
<description>doudou-spring-boot-starter-autoconfigure</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
DoudouAutoConfiguration:
@Configuration
@EnableConfigurationProperties(DouDouProperties.class)
@ConditionalOnProperty(value = "doudou.name")
public class DoudouAutoConfiguration {
}
DouDouProperties:
@ConfigurationProperties("doudou")
public class DouDouProperties {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
DouDouController:
@RestController
public class DouDouController {
@Autowired
private DouDouProperties douDouProperties;
@RequestMapping("/test/doudou/custom/starter")
public String indexController() {
return douDouProperties.getName();
}
}
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.doudou.configuration.DoudouAutoConfiguration
外部程序依赖自定义的starter
额外搭建一个SpringBoot工程,依赖doudou-spring-boot-starter
备注:这个搭建比较简单,我主要展示效果
第一步:增加pom依赖
<dependency>
<groupId>com.doudou</groupId>
<artifactId>doudou-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
其次在application.yml增加配置:
doudou:
name: doudou
启动服务,访问starter提供的接口
6、总结
如何在面试中回答SpringBoot自动配置原理?
我会从核心注解入手,分析原理:
1、@SpringBootApplication注解标注该工程是一个SpringBoot工程,该注解包含两个核心注解@SpringBootConfiguration、@EnableAutoConfiguration;
2、@SpringBootConfiguration本质就是@Configuration,表示是一个配置类;
3、@EnableAutoConfiguration注解,包含两个核心注解@AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class);
4、@AutoConfigurationPackage注解核心在于@Import(AutoConfigurationPackages.Register.class),该Register类实现了ImportBeanDefinitionRegister接口,我们知道实现该接口,实际上就是手动注册BeanDefinition至Spring IOC容器中,SpringBoot通过@Import(AutoConfigurationPackages.Register.class)实现,将启动类所在目录注册到Spring IOC容器中;这样,我们项目定义的class就能够被Spring IOC注册成bean;
5、@Import(AutoConfigurationImportSelector.class)注解,其中AutoConfigurationImportSelector类实现了DeferredImportSelector接口,DeferredImportSelector接口是ImportSelector的子接口,我们知道实现了ImportSelector接口,主要目的在于手动导入类将其注册成为bean,而DefeeredImportSelector多了一个延时导入的特性,主要目的在于延时导入各种starter jar包下META-INF/spring.factories里面key为EnableAutoConfiuration的配置类,再按照一定过滤规则,过滤出最终需要自动配置的配置类;为什么要延时导入,因为如果外部自定义的配置类跟SpringBoot自动配置的一样,那么注册外部自定义的配置类至Spring IOC容器中,SpringBoot自动配置的无效;
参考博客
1、mp.weixin.qq.com/s?__biz=Mzg…
源码下载地址
转载自:https://juejin.cn/post/7278238875457355834