彻底搞懂 Spring Boot 自动配置原理:从源码拆解到手写 Starter,零废话全干货

简介: 本文深入解析SpringBoot自动配置原理,基于SpringBoot 3.4.2版本详细拆解了自动配置的执行流程。主要内容包括:1)自动配置的本质是基于条件注解的动态JavaConfig配置类;2)核心执行流程通过AutoConfigurationImportSelector实现;3)SpringBoot 3.x采用新的自动配置注册方式;4)重点讲解了@Conditional系列条件注解的使用场景与常见坑点;5)通过开发自定义加密Starter实战演示完整实现过程。

Spring Boot 凭借“约定大于配置”的核心理念,彻底解决了传统Spring框架繁琐的XML配置问题,让Java企业级开发效率实现质的飞跃。而自动配置(AutoConfiguration)作为Spring Boot的灵魂核心,是所有开发者入门必学、进阶必懂的知识点。本文将从底层源码出发,用通俗的语言拆解自动配置的完整执行流程,配合可直接运行的实战示例,帮你彻底吃透自动配置原理,同时解决日常开发中的常见坑点。

一、自动配置到底是什么?和手动配置的核心区别

传统Spring开发中,比如整合MyBatis,需要手动配置DataSource、SqlSessionFactory、MapperScannerConfigurer等核心Bean,要么编写冗长的XML文件,要么通过@Configuration+@Bean手动注册,每个第三方依赖都要重复编写配置代码,不仅效率低下,还极易出现配置错误。

而Spring Boot自动配置,是框架根据项目引入的依赖包(Starter),自动判断需要注册的Bean组件,自动完成配置参数的绑定与Bean的初始化,开发者直接注入即可使用,无需手动编写任何配置代码。

其核心本质是:一套基于条件注解的、可动态加载的JavaConfig配置类,由Spring Boot在项目启动时自动扫描、条件匹配后,批量注册到Spring IOC容器中。

二、自动配置的核心前置知识

想要彻底搞懂自动配置原理,必须先掌握以下5个核心前置知识点,否则无法理解源码的执行逻辑。

  1. JavaConfig配置类:@Configuration注解标注的类,用于替代传统XML配置,通过@Bean注解标注的方法向容器注册Bean。Spring Boot 3.x推荐使用@AutoConfiguration注解,该注解继承自@Configuration,默认设置proxyBeanMethods = false,关闭CGLIB代理,大幅提升配置类的解析性能。
  2. @Import注解:Spring框架的核心注解,用于向容器中导入指定类,支持导入普通Java类、配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类。自动配置的核心入口就是@Import(AutoConfigurationImportSelector.class)。
  3. @Conditional条件注解:Spring 4.0引入的条件控制注解,只有满足指定的匹配条件,才会向容器注册对应的Bean。Spring Boot在此基础上扩展了大量业务常用的条件注解,是自动配置的“动态开关”。
  4. Spring Boot的SPI加载机制:SPI(Service Provider Interface)是一种服务发现机制,Spring Boot通过SpringFactoriesLoader工具类,加载类路径下META-INF目录中的配置文件,获取需要注册的自动配置类全限定名。 重点注意:Spring Boot 2.7版本引入了新的自动配置注册方式,需在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中编写自动配置类全限定名;Spring Boot 3.x版本开始,完全移除了spring.factories文件对自动配置类的支持,必须使用新的imports文件注册,旧教程中的spring.factories方式在3.x中已完全失效。
  5. 配置绑定:@ConfigurationProperties注解,用于将application.yml/application.properties配置文件中的属性,批量绑定到Java对象的字段上,配合@EnableConfigurationProperties注解使用,实现配置与业务代码的解耦。

三、自动配置核心源码全拆解

本文所有源码均基于Spring Boot 3.4.2最新稳定版,100%匹配官方源码逻辑,无任何错误解读。

Spring Boot项目的启动入口,是标注了@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
{
   Class<?>[] exclude() default {};
   String[] excludeName() default {};
   String[] scanBasePackages() default {};
   Class<?>[] scanBasePackageClasses() default {};
   boolean proxyBeanMethods() default true;
}

可以看到,@SpringBootApplication是一个复合注解,核心功能由三个注解实现:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,我们逐个拆解核心逻辑。

  1. @SpringBootConfiguration:本质就是一个标准的@Configuration注解,作用是标注启动类是一个Spring配置类,容器启动时会优先解析这个类。源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
   boolean proxyBeanMethods() default true;
}

  1. @ComponentScan:Spring框架的包扫描注解,默认扫描启动类所在包及其子包下所有标注了@Component、@Service、@Controller、@Repository等注解的类,将其注册到IOC容器中。注解中的excludeFilters用于排除自动配置类,避免被重复扫描导致解析顺序混乱。
  2. @EnableAutoConfiguration自动配置的核心注解,没有这个注解,自动配置完全失效。我们看它的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
{
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
   Class<?>[] exclude() default {};
   String[] excludeName() default {};
}

这里有两个核心注解,直接决定了自动配置的执行:

  • @AutoConfigurationPackage:用于注册自动配置的基础包路径,将启动类所在的包路径注册到容器中,供后续组件使用,比如MyBatisPlus的Mapper扫描,就是从这里获取默认的包路径。
  • @Import(AutoConfigurationImportSelector.class):导入了AutoConfigurationImportSelector类,这个类是自动配置的核心入口,所有自动配置类的加载、过滤、排序,全由这个类完成。

3.1 AutoConfigurationImportSelector核心执行逻辑

AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector是ImportSelector的子接口,它的核心特性是:所有用户自定义的@Configuration配置类全部解析完成后,才会执行selectImports方法

这是@ConditionalOnMissingBean注解能生效的核心原因!用户自定义的Bean会先注册到容器中,自动配置类执行时,@ConditionalOnMissingBean就能判断到用户已注册对应Bean,从而不再注册默认Bean,实现用户自定义Bean对自动配置默认Bean的覆盖。

我们先看selectImports核心方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
       return NO_IMPORTS;
   }
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

核心逻辑全部封装在getAutoConfigurationEntry方法中,我们拆解这个方法的8个核心执行步骤:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
   }
   // 步骤1:获取@EnableAutoConfiguration注解的exclude和excludeName属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 步骤2:加载所有候选的自动配置类,从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件读取
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 步骤3:去除重复的自动配置类
   configurations = removeDuplicates(configurations);
   // 步骤4:获取需要排除的自动配置类,从注解属性和配置文件中获取
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   // 步骤5:移除需要排除的自动配置类
   configurations.removeAll(exclusions);
   // 步骤6:应用配置过滤器,通过@Conditional条件注解过滤不符合条件的配置类
   configurations = getConfigurationClassFilter().filter(configurations);
   // 步骤7:触发自动配置导入事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   // 步骤8:封装并返回符合条件的自动配置条目
   return new AutoConfigurationEntry(configurations, exclusions);
}

我们对核心步骤做深度拆解,确保100%理解底层逻辑:

  1. 步骤2:加载候选自动配置类getCandidateConfigurations方法负责加载所有候选的自动配置类,Spring Boot 3.x中已完全使用新的加载方式,源码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
       .getCandidates()
;
   if (configurations.isEmpty()) {
       throw new IllegalArgumentException(
           "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. "
               + "If you are using a custom packaging, make sure that file is correct.");
   }
   return configurations;
}

这里明确了Spring Boot 3.x的自动配置类加载路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,不再从spring.factories文件中加载自动配置类,旧教程中的相关内容在3.x中已完全失效,务必注意。

  1. 步骤6:条件过滤自动配置类这里的配置过滤器会解析自动配置类上的@Conditional系列注解,逐一判断条件是否满足,只有全部条件满足的自动配置类,才会被保留下来交给Spring容器解析。

我们以Spring Boot官方的DataSourceAutoConfiguration自动配置类为例,看它的条件注解配置:

@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type
= "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import(
{ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration
{
   // 省略内部配置类
}

这个自动配置类,只有当类路径下存在DataSource和EmbeddedDatabaseType类(即引入了jdbc相关依赖),且容器中没有R2DBC的ConnectionFactory Bean时,才会生效,否则会被过滤掉,不会被容器解析。

当AutoConfigurationImportSelector返回符合条件的自动配置类全限定名后,Spring容器会将这些类作为配置类,解析其中的@Bean方法,将对应的Bean实例注册到IOC容器中,整个自动配置流程完成。

3.2 自动配置完整执行流程图

四、核心条件注解全解析与易混淆点区分

Spring Boot扩展了大量@Conditional的子注解,用于控制自动配置类和Bean的注册条件,这里我们讲解最常用的注解,以及开发中90%的人都会踩的坑点。

4.1 常用条件注解详解

注解名称 核心作用 典型使用场景
@ConditionalOnClass 类路径下存在指定的类时,配置生效 依赖包存在时才注册对应Bean,避免ClassNotFoundException
@ConditionalOnMissingClass 类路径下不存在指定的类时,配置生效 排除特定依赖场景下的配置
@ConditionalOnBean Spring容器中存在指定类型/名称的Bean时,配置生效 依赖其他Bean存在时才注册当前Bean
@ConditionalOnMissingBean Spring容器中不存在指定类型/名称的Bean时,配置生效 注册默认Bean,允许用户自定义Bean覆盖
@ConditionalOnProperty 配置文件中存在指定属性且值匹配时,配置生效 通过配置文件开关控制配置是否生效
@ConditionalOnWebApplication 当前应用是Web应用时,配置生效 Web环境下才注册的Bean,如Controller、拦截器
@ConditionalOnNotWebApplication 当前应用不是Web应用时,配置生效 非Web环境下才注册的Bean
@ConditionalOnResource 类路径下存在指定资源文件时,配置生效 依赖配置文件存在时才生效的配置
@ConditionalOnSingleCandidate 容器中存在且仅存在一个指定类型的Bean时,配置生效 自动注入依赖时,确保唯一候选Bean

4.2 易混淆点与坑点详解

4.2.1 @ConditionalOnBean vs @ConditionalOnMissingBean 核心区别与坑

这两个注解是日常开发中最容易踩坑的,我们做深度拆解:

  1. 核心区别
  • @ConditionalOnBean:容器中已存在指定Bean时,才注册当前Bean,是“存在才生效”。
  • @ConditionalOnMissingBean:容器中不存在指定Bean时,才注册当前Bean,是“不存在才生效”,主要用于注册默认Bean,允许用户自定义覆盖。
  1. 最常见的坑:执行顺序问题前面我们讲过,AutoConfigurationImportSelector实现了DeferredImportSelector,执行时机在所有用户自定义配置类解析完成之后,所以:
  • 正确用法:@ConditionalOnMissingBean只能用在自动配置类中,不能用在被@ComponentScan扫描到的用户自定义配置类中。
  • 错误用法:如果在用户自定义配置类中使用@ConditionalOnMissingBean,由于用户配置类解析顺序在自动配置类之前,此时容器中还没有注册自动配置的Bean,@ConditionalOnMissingBean会判断为“不存在”,注册用户的Bean,虽然看起来结果正常,但多个配置类存在依赖时,会出现顺序混乱,导致Bean注册失败。
  1. 第二个坑:类型匹配问题@ConditionalOnMissingBean默认按类型匹配,如果要按名称匹配,必须指定name属性,很多人写错导致注解失效。 正确示例:

// 按类型匹配,容器中没有EncryptService类型的Bean时,才注册
@Bean
@ConditionalOnMissingBean
public EncryptService encryptService(EncryptProperties properties) {
   return new EncryptService(properties);
}
// 按名称匹配,容器中名称为encryptService的Bean不存在时,才注册
@Bean
@ConditionalOnMissingBean(name = "encryptService")
public EncryptService encryptService(EncryptProperties properties) {
   return new EncryptService(properties);
}

  1. 第三个坑:同配置类内匹配失效@ConditionalOnBean只能匹配到已被容器完成注册的Bean,如果两个Bean在同一个配置类中,@ConditionalOnBean无法匹配到同一个配置类中前面定义的Bean,因为配置类的解析是先解析所有@Bean方法,再统一注册Bean,所以同一个配置类中,@ConditionalOnBean无法生效,必须拆分到不同的配置类中,通过@AutoConfigureAfter控制解析顺序。

4.2.2 @AutoConfigureOrder vs @Order 核心区别

很多人会用@Order注解控制自动配置类的顺序,这是完全错误的!

  • @AutoConfigureOrder/@AutoConfigureBefore/@AutoConfigureAfter:专门用于控制自动配置类的解析顺序,只能用在自动配置类上,Spring Boot会在过滤完自动配置类后,根据这些注解进行排序,确保依赖的自动配置类先解析。
  • @Order注解:只能控制Bean的注册顺序,无法控制配置类的解析顺序,用在配置类上完全无效,这是90%的开发者都会踩的坑!

正确用法:如果你的自动配置类依赖DataSourceAutoConfiguration,必须在DataSourceAutoConfiguration解析完成后再解析,写法如下:

@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyBatisPlusAutoConfiguration
{
   // 省略配置内容
}

4.2.3 @ConditionalOnProperty的matchIfMissing属性

@ConditionalOnProperty注解有一个非常重要的属性matchIfMissing,默认值为false,含义是:如果配置文件中没有这个属性,条件不成立,配置不生效。

如果我们希望配置文件中没有这个属性时,配置默认生效,就需要设置matchIfMissing = true,这是很多人配置不生效的常见原因。 正确示例:

// 配置文件中存在jam.encrypt.enabled=true时生效,无该配置时默认不生效
@ConditionalOnProperty(prefix = "jam.encrypt", name = "enabled", havingValue = "true")
// 配置文件中存在jam.encrypt.enabled=true时生效,无该配置时默认生效
@ConditionalOnProperty(prefix = "jam.encrypt", name = "enabled", havingValue = "true", matchIfMissing = true)

五、实战:手写自定义Spring Boot Starter,吃透自动配置

理论讲完,我们通过手写一个可直接运行的自定义Starter,巩固自动配置的所有知识点。这个Starter实现了配置化的字符串加密解密功能,完全符合开发规范,可直接编译运行。

5.1 环境与版本说明

  • JDK版本:17
  • Spring Boot版本:3.4.2
  • 项目管理:Maven

5.2 项目结构

我们分为两个模块开发:

  1. jam-spring-boot-starter:自定义Starter核心模块,实现自动配置逻辑
  2. jam-spring-boot-starter-test:测试模块,引入Starter,验证功能可用性

5.2.1 自定义Starter模块(jam-spring-boot-starter)

首先编写pom.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>3.4.2</version>
       <relativePath/>
   </parent>
   <groupId>com.jam.demo</groupId>
   <artifactId>jam-spring-boot-starter</artifactId>
   <version>1.0.0</version>
   <name>jam-spring-boot-starter</name>
   <description>自定义Spring Boot Starter 加密工具</description>
   <properties>
       <java.version>17</java.version>
       <lombok.version>1.18.34</lombok.version>
       <guava.version>33.2.0-jre</guava.version>
       <fastjson2.version>2.0.52</fastjson2.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-configuration-processor</artifactId>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
   </dependencies>
</project>

编写配置属性绑定类,用于绑定application.yml中的配置:

package com.jam.demo.starter.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* 加密工具配置属性类
* 用于绑定application.yml中jam.encrypt前缀的配置
* @author ken
*/

@ConfigurationProperties(prefix = "jam.encrypt")
public class EncryptProperties {
   /**
    * 加密密钥,默认值:defaultSecretKey123456
    */

   private String secretKey = "defaultSecretKey123456";
   /**
    * 加密算法,默认值:AES
    */

   private String algorithm = "AES";
   /**
    * 是否开启加密工具,默认开启
    */

   private Boolean enabled = true;

   public String getSecretKey() {
       return secretKey;
   }

   public void setSecretKey(String secretKey) {
       this.secretKey = secretKey;
   }

   public String getAlgorithm() {
       return algorithm;
   }

   public void setAlgorithm(String algorithm) {
       this.algorithm = algorithm;
   }

   public Boolean getEnabled() {
       return enabled;
   }

   public void setEnabled(Boolean enabled) {
       this.enabled = enabled;
   }
}

编写核心服务类,实现加密解密功能:

package com.jam.demo.starter.service;

import com.jam.demo.starter.config.EncryptProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
* 加密解密核心服务类
* @author ken
*/

@Slf4j
public class EncryptService {
   private final EncryptProperties encryptProperties;
   private static final int AES_KEY_LENGTH = 16;

   /**
    * 构造方法注入配置属性
    * @param encryptProperties 加密配置属性
    */

   public EncryptService(EncryptProperties encryptProperties) {
       this.encryptProperties = encryptProperties;
   }

   /**
    * 字符串加密方法
    * @param content 待加密的明文内容
    * @return 加密后的Base64编码字符串
    * @throws Exception 加密异常
    */

   public String encrypt(String content) throws Exception {
       if (!StringUtils.hasText(content)) {
           log.warn("待加密内容为空,直接返回原内容");
           return content;
       }
       if (ObjectUtils.isEmpty(encryptProperties.getEnabled()) || !encryptProperties.getEnabled()) {
           log.info("加密工具未开启,直接返回原内容");
           return content;
       }
       SecretKeySpec keySpec = buildSecretKeySpec();
       Cipher cipher = Cipher.getInstance(encryptProperties.getAlgorithm());
       cipher.init(Cipher.ENCRYPT_MODE, keySpec);
       byte[] encryptedBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
       String result = Base64.getEncoder().encodeToString(encryptedBytes);
       log.debug("内容加密成功");
       return result;
   }

   /**
    * 字符串解密方法
    * @param encryptedContent 待解密的Base64编码字符串
    * @return 解密后的明文内容
    * @throws Exception 解密异常
    */

   public String decrypt(String encryptedContent) throws Exception {
       if (!StringUtils.hasText(encryptedContent)) {
           log.warn("待解密内容为空,直接返回原内容");
           return encryptedContent;
       }
       if (ObjectUtils.isEmpty(encryptProperties.getEnabled()) || !encryptProperties.getEnabled()) {
           log.info("加密工具未开启,直接返回原内容");
           return encryptedContent;
       }
       SecretKeySpec keySpec = buildSecretKeySpec();
       Cipher cipher = Cipher.getInstance(encryptProperties.getAlgorithm());
       cipher.init(Cipher.DECRYPT_MODE, keySpec);
       byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedContent));
       String result = new String(decryptedBytes, StandardCharsets.UTF_8);
       log.debug("内容解密成功");
       return result;
   }

   /**
    * 构建加密密钥规范
    * @return SecretKeySpec 密钥规范
    */

   private SecretKeySpec buildSecretKeySpec() {
       String secretKey = encryptProperties.getSecretKey();
       if (!StringUtils.hasText(secretKey)) {
           throw new IllegalArgumentException("加密密钥不能为空");
       }
       byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
       byte[] validKeyBytes = new byte[AES_KEY_LENGTH];
       System.arraycopy(keyBytes, 0, validKeyBytes, 0, Math.min(keyBytes.length, AES_KEY_LENGTH));
       return new SecretKeySpec(validKeyBytes, encryptProperties.getAlgorithm());
   }
}

编写核心自动配置类,这是Starter的核心:

package com.jam.demo.starter.autoconfigure;

import com.jam.demo.starter.config.EncryptProperties;
import com.jam.demo.starter.service.EncryptService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
* 加密工具自动配置类
* @author ken
*/

@AutoConfiguration
@ConditionalOnClass(EncryptService.class)
@EnableConfigurationProperties(EncryptProperties.class)
@ConditionalOnProperty(prefix
= "jam.encrypt", name = "enabled", havingValue = "true", matchIfMissing = true)
public class EncryptAutoConfiguration {

   /**
    * 注册加密服务Bean,当容器中没有EncryptService类型的Bean时生效
    * @param encryptProperties 加密配置属性
    * @return EncryptService 加密服务实例
    */

   @Bean
   @ConditionalOnMissingBean
   public EncryptService encryptService(EncryptProperties encryptProperties) {
       return new EncryptService(encryptProperties);
   }
}

最后编写自动配置类的注册文件,Spring Boot 3.x必须使用该文件,否则自动配置不生效: 在resources目录下创建META-INF/spring目录,新建org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,内容如下:

com.jam.demo.starter.autoconfigure.EncryptAutoConfiguration

到这里,自定义Starter开发完成,执行mvn clean install命令,将Starter安装到本地Maven仓库,即可在其他项目中引入使用。

5.2.2 测试模块(jam-spring-boot-starter-test)

我们创建Spring Boot Web项目,引入上面的Starter,配合Swagger3和MyBatisPlus,完成功能测试,所有代码可直接运行。

首先编写pom.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>3.4.2</version>
       <relativePath/>
   </parent>
   <groupId>com.jam.demo</groupId>
   <artifactId>jam-spring-boot-starter-test</artifactId>
   <version>1.0.0</version>
   <name>jam-spring-boot-starter-test</name>
   <description>自定义Starter测试项目</description>
   <properties>
       <java.version>17</java.version>
       <lombok.version>1.18.34</lombok.version>
       <mybatis-plus.version>3.5.7</mybatis-plus.version>
       <mysql.version>8.4.0</mysql.version>
       <springdoc.version>2.6.0</springdoc.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>com.jam.demo</groupId>
           <artifactId>jam-spring-boot-starter</artifactId>
           <version>1.0.0</version>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <version>${mysql.version}</version>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>${springdoc.version}</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <configuration>
                   <excludes>
                       <exclude>
                           <groupId>org.projectlombok</groupId>
                           <artifactId>lombok</artifactId>
                       </exclude>
                   </excludes>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

编写application.yml配置文件,配置加密工具、数据源、Swagger等:

server:
 port: 8080
jam:
 encrypt:
   secret-key: jamDemo123456789
   algorithm: AES
   enabled: true
spring:
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql://127.0.0.1:3306/jam_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
   username: root
   password: root
mybatis-plus:
 mapper-locations: classpath:mapper/*.xml
 type-aliases-package: com.jam.demo.test.entity
 configuration:
   map-underscore-to-camel-case: true
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
 swagger-ui:
   path: /swagger-ui.html
   enabled: true
 api-docs:
   enabled: true
   path: /v3/api-docs

编写MySQL 8.0可直接执行的表结构SQL:

CREATE DATABASE IF NOT EXISTS jam_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE jam_demo;
DROP TABLE IF EXISTS sys_user;
CREATE TABLE sys_user (
   id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
   username VARCHAR(64) NOT NULL COMMENT '用户名',
   password VARCHAR(256) NOT NULL COMMENT '密码',
   real_name VARCHAR(32) COMMENT '真实姓名',
   phone VARCHAR(11) COMMENT '手机号',
   create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
   PRIMARY KEY (id),
   UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';
INSERT INTO sys_user (username, password, real_name, phone) VALUES
('test01', '123456', '测试用户01', '13800138000'),
('test02', '123456', '测试用户02', '13800138001');

编写实体类,带MyBatisPlus注解:

package com.jam.demo.test.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.time.LocalDateTime;

/**
* 系统用户实体类
* @author ken
*/

@Data
@TableName("sys_user")
@Schema(description = "系统用户实体")
public class SysUser {
   @TableId(type = IdType.AUTO)
   @Schema(description = "主键ID", example = "1")
   private Long id;
   @Schema(description = "用户名", example = "test01")
   private String username;
   @Schema(description = "密码", example = "123456")
   private String password;
   @Schema(description = "真实姓名", example = "测试用户01")
   private String realName;
   @Schema(description = "手机号", example = "13800138000")
   private String phone;
   @Schema(description = "创建时间")
   private LocalDateTime createTime;
   @Schema(description = "更新时间")
   private LocalDateTime updateTime;
}

编写Mapper接口,继承MyBatisPlus的BaseMapper:

package com.jam.demo.test.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.test.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;

/**
* 系统用户Mapper接口
* @author ken
*/

@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}

编写Service接口和实现类:

package com.jam.demo.test.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.test.entity.SysUser;

/**
* 系统用户服务接口
* @author ken
*/

public interface SysUserService extends IService<SysUser> {
}

package com.jam.demo.test.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.test.entity.SysUser;
import com.jam.demo.test.mapper.SysUserMapper;
import com.jam.demo.test.service.SysUserService;
import org.springframework.stereotype.Service;

/**
* 系统用户服务实现类
* @author ken
*/

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
}

编写Controller,注入自定义的EncryptService,带Swagger3注解:

package com.jam.demo.test.controller;

import com.jam.demo.starter.service.EncryptService;
import com.jam.demo.test.entity.SysUser;
import com.jam.demo.test.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 加密测试Controller
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/api/encrypt")
@RequiredArgsConstructor
@Tag(name = "加密工具测试接口", description = "自定义Starter加密解密功能测试")
public class EncryptTestController {
   private final EncryptService encryptService;
   private final SysUserService sysUserService;

   /**
    * 字符串加密接口
    * @param content 待加密的明文
    * @return 加密后的密文
    * @throws Exception 加密异常
    */

   @GetMapping("/encrypt")
   @Operation(summary = "字符串加密", description = "对输入的明文字符串进行加密")
   public ResponseEntity<String> encrypt(
           @Parameter(description = "待加密的明文", required = true, example = "123456")

           @RequestParam String content) throws Exception {
       String encryptedContent = encryptService.encrypt(content);
       log.info("加密完成,明文:{},密文:{}", content, encryptedContent);
       return ResponseEntity.ok(encryptedContent);
   }

   /**
    * 字符串解密接口
    * @param encryptedContent 待解密的密文
    * @return 解密后的明文
    * @throws Exception 解密异常
    */

   @GetMapping("/decrypt")
   @Operation(summary = "字符串解密", description = "对输入的密文字符串进行解密")
   public ResponseEntity<String> decrypt(
           @Parameter(description = "待解密的密文", required = true)

           @RequestParam String encryptedContent) throws Exception {
       String content = encryptService.decrypt(encryptedContent);
       log.info("解密完成,密文:{},明文:{}", encryptedContent, content);
       return ResponseEntity.ok(content);
   }

   /**
    * 查询所有用户,密码加密返回
    * @return 用户列表
    */

   @GetMapping("/user/list")
   @Operation(summary = "查询用户列表", description = "查询所有用户,密码字段加密返回")
   public ResponseEntity<List<SysUser>> getUserList() throws Exception {
       List<SysUser> userList = sysUserService.list();
       for (SysUser user : userList) {
           user.setPassword(encryptService.encrypt(user.getPassword()));
       }
       return ResponseEntity.ok(userList);
   }
}

编写项目启动类:

package com.jam.demo.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* 项目启动类
* @author ken
*/

@SpringBootApplication
@MapperScan("com.jam.demo.test.mapper")
public class JamStarterTestApplication {
   public static void main(String[] args) {
       SpringApplication.run(JamStarterTestApplication.class, args);
   }
}

5.3 项目运行与测试

  1. 先执行mvn clean install将jam-spring-boot-starter安装到本地Maven仓库。
  2. 在MySQL 8.0中执行上面的SQL脚本,创建数据库和表。
  3. 修改application.yml中的数据库用户名和密码为本地配置。
  4. 启动JamStarterTestApplication启动类,项目启动成功后,访问http://localhost:8080/swagger-ui.html,打开Swagger界面即可测试所有接口,验证加密解密功能正常。
  5. 在application.yml中设置jam.encrypt.enabled=false,重启项目,可验证加密功能关闭,接口直接返回原内容,@ConditionalOnProperty注解生效。
  6. 在项目中自定义一个EncryptService的Bean,重启项目,可验证Spring Boot使用自定义Bean,而非自动配置的默认Bean,@ConditionalOnMissingBean注解生效。

六、自动配置常见问题排查与最佳实践

6.1 自动配置不生效的排查步骤

  1. 开启自动配置报告:在application.yml中设置debug=true,启动项目,控制台会打印自动配置报告,列出所有自动配置类的生效状态和不生效的原因,这是最直接的排查方式。
  2. 检查自动配置类的注册文件:Spring Boot 3.x必须在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中正确编写自动配置类的全限定名,检查包名是否正确,有无拼写错误。
  3. 检查@Conditional条件是否满足:根据自动配置报告的原因,检查条件注解的条件是否满足,比如类路径下是否有对应类、配置文件中的属性是否正确、容器中是否有对应Bean。
  4. 检查是否被@ComponentScan重复扫描:如果自动配置类被启动类的@ComponentScan扫描到,会变成用户自定义配置类,解析顺序提前,导致@ConditionalOnMissingBean失效,自动配置类的包名不要和启动类包名一致,避免被扫描。
  5. 检查自动配置类的顺序:如果自动配置类依赖其他自动配置类,必须用@AutoConfigureAfter注解指定依赖的配置类,确保解析顺序正确。
  6. 检查配置绑定是否正确:@ConfigurationProperties注解的prefix是否正确,字段的getter/setter方法是否齐全,有无拼写错误。

6.2 自动配置最佳实践

  1. 自动配置类使用@AutoConfiguration注解,不要使用@Configuration注解,@AutoConfiguration默认设置proxyBeanMethods = false,关闭CGLIB代理,提升性能,是Spring Boot 3.x推荐的写法。
  2. 自动配置类不要被@ComponentScan扫描到,放在单独的包中,只通过imports文件注册,避免解析顺序混乱。
  3. 优先使用@ConditionalOnClass注解做前置判断,确保类路径下存在对应类,避免ClassNotFoundException。
  4. @ConditionalOnMissingBean只用于注册默认Bean,允许用户自定义Bean覆盖,不要用于强制Bean的唯一性。
  5. 自动配置类的顺序使用@AutoConfigureBefore/@AutoConfigureAfter注解控制,不要使用@Order注解,@Order无法控制配置类的解析顺序。
  6. 配置绑定使用@ConfigurationProperties注解,配合spring-boot-configuration-processor生成配置元数据,让IDE有代码提示,提升开发体验。
  7. 自动配置类要保持职责单一,一个自动配置类只负责一个组件的配置,不要把多个组件的配置放在同一个类中。
  8. 提供自动配置的排除方式,通过@EnableAutoConfiguration的exclude属性和spring.autoconfigure.exclude配置,允许用户关闭不需要的自动配置。

七、总结

Spring Boot自动配置的核心本质,是基于Spring的JavaConfig配置类、@Import注解、@Conditional条件注解,配合SPI服务发现机制,实现了配置的自动化加载和注册。它的核心逻辑并不复杂,只要搞懂了它的执行流程、核心注解的作用、以及常见的坑点,就能轻松驾驭Spring Boot的自动配置,不仅能解决日常开发中的问题,还能根据业务需求开发自定义Starter,大幅提升开发效率。

目录
相关文章
|
5天前
|
人工智能 安全 API
CoPaw:5分钟部署你的 AI助理
源自阿里巴巴开源生态的个人 AI 助理——CoPaw。作为阿里倾力打造的开源力作,CoPaw 完美打通钉钉、飞书、Discord 等多平台对话通道,支持定时任务自动化。内置 PDF/Office 深度处理、新闻摘要等强大技能,更开放自定义扩展接口。坚持数据全程私有化部署,绝不上传云端,让每一位用户都能在大厂技术加持下,拥有安全、专属的智能助手。
|
8天前
|
人工智能 自然语言处理 JavaScript
2026年Windows+Ollama本地部署OpenClaw保姆级教程:本地AI Agent+阿里云上快速搭建
2026年OpenClaw凭借本地部署、私有化运行的特性,成为打造个人智能体的核心工具,而Ollama作为轻量级本地大模型管理工具,能让OpenClaw摆脱对云端大模型的依赖,实现**本地推理、数据不泄露、全流程私有化**的智能体验。本文基于Windows 11系统,从硬件环境准备、Ollama安装与模型定制、OpenClaw部署配置、技能扩展到常见问题排查,打造保姆级本地部署教程,同时补充阿里云OpenClaw(Clawdbot)快速部署步骤,兼顾本地私有化需求与云端7×24小时运行需求,文中所有代码命令均可直接复制执行,确保零基础用户也能快速搭建属于自己的本地智能体。
9406 76
|
6天前
|
人工智能 安全 JavaScript
阿里云上+本地部署OpenClaw(小龙虾)新手攻略:解锁10大必备Skills,零基础也能玩转AI助手
2026年,开源AI代理工具OpenClaw(昵称“小龙虾”)凭借“能实际做事”的核心优势,在GitHub斩获25万+星标,成为现象级AI工具。它最强大的魅力在于可扩展的Skills(技能包)系统——通过ClawHub插件市场的数百个技能,能让AI助手从简单聊天升级为处理办公、学习、日常事务的全能帮手。
4793 13
|
7天前
|
人工智能 自然语言处理 机器人
保姆级教程:Mac本地搭建OpenClaw及阿里云上1分钟部署OpenClaw+飞书集成实战指南
OpenClaw(曾用名Clawdbot、Moltbot)作为2026年最热门的开源个人AI助手平台,以“自然语言驱动自动化”为核心,支持对接飞书、Telegram等主流通讯工具,可替代人工完成文件操作、日历管理、邮件处理等重复性工作。其模块化架构适配多系统环境,既可以在Mac上本地化部署打造私人助手,也能通过阿里云实现7×24小时稳定运行,完美兼顾隐私性与便捷性。
4921 11
|
9天前
|
人工智能 JSON JavaScript
手把手教你用 OpenClaw + 飞书,打造专属 AI 机器人
手把手教你用 OpenClaw(v2026.2.22-2)+ 飞书,10分钟零代码搭建专属AI机器人!内置飞书插件,无需额外安装;支持Claude等主流模型,命令行一键配置。告别复杂开发,像聊同事一样自然对话。
5236 13
手把手教你用 OpenClaw + 飞书,打造专属 AI 机器人
|
8天前
|
人工智能 监控 机器人
2026年零门槛部署 OpenClaw(Clawdbot)接入A股数据,实现24小时股票分析保姆级教程
在AI赋能金融分析的浪潮中,OpenClaw(原Clawdbot/Moltbot)凭借开源灵活的架构,成为个人投资者打造专属智能分析助手的首选。通过接入A股实时数据,它能实现24小时市场监控、涨跌预警、潜力股推荐等核心功能,彻底解放人工盯盘的繁琐。而阿里云的稳定部署环境,更让这套系统实现全天候不间断运行,成为真正的“金融AI助手”。 本文基于OpenClaw v2026.1.25稳定版与QVeris免费A股数据接口,详细拆解阿里云OpenClaw部署步骤、A股数据接入流程、高级分析功能配置及多平台联动技巧,所有代码命令均可直接复制复用,即使无技术基础也能在1小时内完成从部署到实战的全流程。
3651 12
|
4天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
2323 6