百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

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...