SpringBoot自动配置原理,以及如何编写自定义的starter
xsobi 2024-12-15 17:30 1 浏览
简介
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、直接导入普通类
- 创建一个普通类
kotlin
复制代码
package com.doudou.imports; public class DouDouBean { public String print() { return "return doudou bean"; } }
- 创建一个配置类,导入刚创建的类
less
复制代码
@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
- 创建普通类
kotlin
复制代码
@Configuration public class DouDouConfig { @Bean public String RedBeanBun() { return "Red Bean Bun"; } @Bean public String OatBag() { return "Oat Bag"; } }
typescript
复制代码
public class DouDouBean { public String print() { return "return doudou bean"; } }
- 实现ImportSelector接口
typescript
复制代码
public class DouDouImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] { DouDouBean.class.getName(), DouDouConfig.class.getName() }; } }
- 创建一个配置类,导入实现了ImportSelector接口的类
less
复制代码
@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不同点在于:
分组 延时导入
- 创建需要延迟导入的配置类
typescript
复制代码
@Configuration public class DeferredConfig { @Bean public String dog() { return "dog"; } }
- 实现DeferredImportSelector接口
typescript
复制代码
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接口的类
less
复制代码
@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容器当中的类
typescript
复制代码
public class DouDefinitionRegistrarBean { public String pig() { return "pig"; } }
- 实现ImportBeanDefinitionRegistrar接口
java
复制代码
public class DouDefinitionRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition definition = new RootBeanDefinition(DouDefinitionRegistrarBean.class); registry.registerBeanDefinition("douDefinitionRegistrarBean", definition); } }
- 创建配置类,导入实现了ImportBeanDefinitionRegistrar接口的类
less
复制代码
@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,代表配置类生效;反之,不生效;
typescript
复制代码
@Configuration public class DouDouConditionBean implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return true; } }
- 创建另一个配置类,依赖上述配置类matches方法来判断是否需要生效
less
复制代码
@Configuration @Conditional({DouDouConditionBean.class}) public class ConditionBeanConfig { }
- 创建配置类,测试ConditionBeanConfig配置类是否被注入
less
复制代码
@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的主配置类
less
复制代码
@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,展开分析下这个注解都由什么部分组成
less
复制代码
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
可以发现,核心的就两个注解 @AutoConfigurationPackage、 @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
首先分析下 @AutoConfigurationPackage
less
复制代码
@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)
kotlin
复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
该类实现了DeferredImportSelector接口,若getImportGroup()方法返回值不为null,将会调用getImportGroup()方法返回值的process()方法;
具体堆栈如下:
makefile
复制代码
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方法
kotlin
复制代码
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方法
scss
复制代码
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
typescript
复制代码
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
less
复制代码
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
typescript
复制代码
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追加以下一条配置项,这样就能够在控制台输出哪些配置类被自动配置;
lua
复制代码
debug: true
控制台输出结果(内容较多,我拣选了核心的出来):
sql
复制代码
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类为例
less
复制代码
@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为例
swift
复制代码
@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属性赋值;
yaml
复制代码
server: port: 8080
5、自定义starter
简介
SpringBoot最强大的功能就是把我们常用的场景抽取成了一个个starter(场景启动器),我们引入SpringBoot为我们提供的这些场景启动器,我们再进行少量的配置就能完成我们的功能。即使是这样,SpringBoot也不能囊括我们所有的场景,往往我们需要自定义starter,来满足我们需要的功能;
如何编写starter
我们参考spring-boot-starter
发现是一个空的jar包,分析下他的pom.xml文件
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
makefile
复制代码
创建一个父工程,和2个Module: 1、doudou-parent (父工程) 2、doudou-spring-boot-starter (子模块,依赖doudou-spring-boot-starter-autoconfigure) 3、doudou-spring-boot-starter-autoconfigure
项目结构概览:
doudou-parent:
xml
复制代码
<?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
复制代码
<?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
复制代码
<?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:
less
复制代码
@Configuration @EnableConfigurationProperties(DouDouProperties.class) @ConditionalOnProperty(value = "doudou.name") public class DoudouAutoConfiguration { }
DouDouProperties:
typescript
复制代码
@ConfigurationProperties("doudou") public class DouDouProperties { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
DouDouController:
kotlin
复制代码
@RestController public class DouDouController { @Autowired private DouDouProperties douDouProperties; @RequestMapping("/test/doudou/custom/starter") public String indexController() { return douDouProperties.getName(); } }
spring.factories:
ini
复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.doudou.configuration.DoudouAutoConfiguration
外部程序依赖自定义的starter
额外搭建一个SpringBoot工程,依赖doudou-spring-boot-starter
备注:这个搭建比较简单,我主要展示效果
第一步:增加pom依赖
xml
复制代码
<dependency> <groupId>com.doudou</groupId> <artifactId>doudou-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
其次在application.yml增加配置:
yaml
复制代码
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自动配置的无效;
原文链接:https://juejin.cn/post/7278238875457355834
相关推荐
- 好用的云函数!后端低代码接口开发,零基础编写API接口
-
前言在开发项目过程中,经常需要用到API接口,实现对数据库的CURD等操作。不管你是专业的PHP开发工程师,还是客户端开发工程师,或者是不懂编程但懂得数据库SQL查询,又或者是完全不太懂技术的人,通过...
- 快速上手:Windows 平台上 cURL 命令的使用方法
-
在工作流程中,为了快速验证API接口有效性,团队成员经常转向直接执行cURL命令的方法。这种做法不仅节省时间,而且促进了团队效率的提升。对于使用Windows系统的用户来说,这里有一套详细...
- 使用 Golang net/http 包:基础入门与实战
-
简介Go的net/http包是构建HTTP服务的核心库,功能强大且易于使用。它提供了基本的HTTP客户端和服务端支持,可以快速构建RESTAPI、Web应用等服务。本文将介绍ne...
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
-
你只需编写简单的云函数,就可以实现自己的业务逻辑,发布后就可以生成自己的接口给客户端调用。果创云支持对云函数进行在线接口编程,进入开放平台我的接口-在线接口编程,设计一个新接口,设计和配置好接口参...
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:iN在之前和大家说过,在iN的家里是没有墙面开关的。...
- window使用curl命令的注意事项 curl命令用法
-
cmd-使用curl命令的注意点前言最近在cmd中使用curl命令来测试restapi,发现有不少问题,这里记录一下。在cmd中使用curl命令的注意事项json不能由单引号包括起来json...
- Linux 系统curl命令使用详解 linuxctrl
-
curl是一个强大的命令行工具,用于在Linux系统中进行数据传输。它支持多种协议,包括HTTP、HTTPS、FTP等,用于下载或上传数据,执行Web请求等。curl命令的常见用法和解...
- Tornado 入门:初学者指南 tornados
-
Tornado是一个功能强大的PythonWeb框架和异步网络库。它最初是为了处理实时Web服务中的数千个同时连接而开发的。它独特的Web服务器和框架功能组合使其成为开发高性能Web...
- PHP Curl的简单使用 php curl formdata
-
本文写给刚入PHP坑不久的新手们,作为工具文档,方便用时查阅。CURL是一个非常强大的开源库,它支持很多种协议,例如,HTTP、HTTPS、FTP、TELENT等。日常开发中,我们经常会需要用到cur...
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
-
本章涵盖使用Actix提供静态网页...
- 我给 Apache 顶级项目提了个 Bug apache顶级项目有哪些
-
这篇文章记录了给Apache顶级项目-分库分表中间件ShardingSphere提交Bug的历程。说实话,这是一次比较曲折的Bug跟踪之旅。10月28日,我们在GitHub上提...
- linux文件下载、服务器交互(curl)
-
基础环境curl命令描述...
- curl简单使用 curl sh
-
1.curl--help#查看关键字2.curl-A“(添加user-agent<name>SendUser-Agent<name>toserver)”...
- 常用linux命令:curl 常用linux命令大全
-
//获取网页内容//不加任何选项使用curl时,默认会发送GET请求来获取内容到标准输出$curlhttp://www.baidu.com//输出<!DOCTYPEh...
- 三十七,Web渗透提高班之hack the box在线靶场注册及入门知识
-
一.注册hacktheboxHackTheBox是一个在线平台,允许测试您的渗透技能和代码,并与其他类似兴趣的成员交流想法和方法。它包含一些不断更新的挑战,并且模拟真实场景,其风格更倾向于CT...
- 一周热门
- 最近发表
-
- 好用的云函数!后端低代码接口开发,零基础编写API接口
- 快速上手:Windows 平台上 cURL 命令的使用方法
- 使用 Golang net/http 包:基础入门与实战
- #小白接口# 使用云函数,人人都能编写和发布自己的API接口
- 极度精神分裂:我家没有墙面开关,但我虚拟出来了一系列开关
- window使用curl命令的注意事项 curl命令用法
- Linux 系统curl命令使用详解 linuxctrl
- Tornado 入门:初学者指南 tornados
- PHP Curl的简单使用 php curl formdata
- Rust 服务器、服务和应用程序:7 Rust 中的服务器端 Web 应用简介
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- strcat (25)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- webgl开发 (24)
- parse (24)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)