19-Spring Boot 自动配置原理详解

2 阅读10分钟

Spring Boot 自动配置原理详解

一、知识概述

Spring Boot 的核心特性之一是自动配置(Auto-Configuration),它根据项目中的依赖和配置,自动配置 Spring 应用程序。这大大简化了 Spring 应用的开发,开发者只需引入相关依赖,Spring Boot 就会自动完成配置工作。

自动配置的核心机制:

  • @EnableAutoConfiguration:启用自动配置
  • spring.factories:自动配置类注册
  • 条件注解:按需加载配置
  • Starter 机制:依赖管理

理解自动配置原理,有助于更好地使用 Spring Boot,并能够自定义 Starter。

二、知识点详细讲解

2.1 @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 {
    // ...
}

三个核心注解:

  1. @SpringBootConfiguration:等价于 @Configuration
  2. @EnableAutoConfiguration:启用自动配置
  3. @ComponentScan:组件扫描

2.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 {};
}

核心组件:

  1. @AutoConfigurationPackage:注册主配置类所在包
  2. AutoConfigurationImportSelector:导入自动配置类

2.3 自动配置类发现机制

启动应用
    ↓
AutoConfigurationImportSelector
    ↓
加载 META-INF/spring.factories
    ↓
获取 EnableAutoConfiguration 配置的类
    ↓
条件过滤
    ↓
注册符合条件的配置类
spring.factories 文件格式
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

2.4 条件注解

Spring Boot 提供了丰富的条件注解来控制配置类的加载:

条件注解说明
@ConditionalOnClass类路径存在指定类时生效
@ConditionalOnMissingClass类路径不存在指定类时生效
@ConditionalOnBean容器中存在指定 Bean 时生效
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效
@ConditionalOnProperty配置属性满足条件时生效
@ConditionalOnResource资源存在时生效
@ConditionalOnWebApplicationWeb 应用时生效
@ConditionalOnExpressionSpEL 表达式为 true 时生效

2.5 Starter 机制

Starter 是 Spring Boot 提供的依赖管理方案,一个 Starter 通常包含:

  • 自动配置类:自动配置相关 Bean
  • 依赖管理:传递相关依赖
  • 配置属性:可配置的属性类
常用 Starter
Starter说明
spring-boot-starter-webWeb 开发
spring-boot-starter-data-jpaJPA 数据访问
spring-boot-starter-data-redisRedis
spring-boot-starter-security安全框架
spring-boot-starter-actuator监控端点

2.6 配置属性绑定

Spring Boot 通过 @ConfigurationProperties 实现属性绑定:

@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private String name;
    private int port;
    // getter/setter
}

对应的配置:

app:
  name: myapp
  port: 8080

三、代码示例

3.1 自动配置示例

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;

// 数据源配置类
@Configuration
@ConditionalOnClass(javax.sql.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    
    private final DataSourceProperties properties;
    
    public DataSourceAutoConfiguration(DataSourceProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return createDataSource();
    }
    
    private DataSource createDataSource() {
        // 创建数据源
        return null;
    }
}

// 数据源属性类
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private int initialSize = 5;
    private int maxActive = 20;
    
    // getter/setter
    public String getUrl() { return url; }
    public void setUrl(String url) { this.url = url; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getDriverClassName() { return driverClassName; }
    public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
    public int getInitialSize() { return initialSize; }
    public void setInitialSize(int initialSize) { this.initialSize = initialSize; }
    public int getMaxActive() { return maxActive; }
    public void setMaxActive(int maxActive) { this.maxActive = maxActive; }
}

3.2 条件注解示例

import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Value;

@Configuration
public class ConditionalConfig {
    
    // 当类路径存在 HikariDataSource 时创建
    @Bean
    @ConditionalOnClass(com.zaxxer.hikari.HikariDataSource.class)
    public DataSource hikariDataSource() {
        System.out.println("创建 Hikari 数据源");
        return null;  // 实际创建逻辑
    }
    
    // 当没有其他 DataSource Bean 时创建默认数据源
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    @ConditionalOnProperty(prefix = "spring.datasource", name = "url")
    public DataSource defaultDataSource() {
        System.out.println("创建默认数据源");
        return null;
    }
    
    // 根据配置属性决定是否创建
    @Bean
    @ConditionalOnProperty(
        prefix = "app.cache", 
        name = "enabled", 
        havingValue = "true",
        matchIfMissing = true  // 如果属性不存在,默认匹配
    )
    public CacheManager cacheManager() {
        System.out.println("创建缓存管理器");
        return null;
    }
    
    // Web 应用时创建
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    public WebHandler webHandler() {
        System.out.println("创建 Web 处理器");
        return null;
    }
    
    // 非 Web 应用时创建
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.NONE)
    public CommandLineRunner cliRunner() {
        System.out.println("创建命令行运行器");
        return args -> System.out.println("命令行应用启动");
    }
    
    // 表达式条件
    @Bean
    @ConditionalOnExpression("${app.feature.enabled:false} and '${app.mode}'.equals('production')")
    public FeatureService productionFeatureService() {
        System.out.println("创建生产环境特性服务");
        return new FeatureService();
    }
    
    // 资源存在时创建
    @Bean
    @ConditionalOnResource(resources = "classpath:config.properties")
    public ConfigLoader configLoader() {
        System.out.println("创建配置加载器");
        return new ConfigLoader();
    }
    
    // 组合条件
    @Bean
    @ConditionalOnClass(name = "redis.clients.jedis.Jedis")
    @ConditionalOnProperty(prefix = "spring.redis", name = "host")
    @ConditionalOnMissingBean(RedisClient.class)
    public RedisClient redisClient() {
        System.out.println("创建 Redis 客户端");
        return new RedisClient();
    }
}

// 辅助类
public class CacheManager {}
public class WebHandler {}
public class FeatureService {}
public class ConfigLoader {}
public class RedisClient {}
public interface DataSource {}

3.3 自定义 Starter

项目结构
my-spring-boot-starter/
├── src/main/java/
│   └── com/example/
│       ├── autoconfigure/
│       │   ├── MyAutoConfiguration.java
│       │   └── MyProperties.java
│       └── MyService.java
└── src/main/resources/
    └── META-INF/
        └── spring.factories
└── pom.xml
自动配置类
package com.example.autoconfigure;

import com.example.MyService;
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.*;

@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
    
    private final MyProperties properties;
    
    public MyAutoConfiguration(MyProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService(
            properties.getName(),
            properties.getEnabled()
        );
    }
}
属性类
package com.example.autoconfigure;

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

@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    
    /**
     * 服务名称
     */
    private String name = "default";
    
    /**
     * 是否启用
     */
    private boolean enabled = true;
    
    /**
     * 超时时间(毫秒)
     */
    private int timeout = 3000;
    
    /**
     * 重试次数
     */
    private int retryCount = 3;
    
    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public boolean getEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }
    public int getRetryCount() { return retryCount; }
    public void setRetryCount(int retryCount) { this.retryCount = retryCount; }
}
服务类
package com.example;

public class MyService {
    
    private final String name;
    private final boolean enabled;
    
    public MyService(String name, boolean enabled) {
        this.name = name;
        this.enabled = enabled;
    }
    
    public String sayHello() {
        if (!enabled) {
            return "Service is disabled";
        }
        return "Hello from " + name + "!";
    }
    
    public String getName() {
        return name;
    }
    
    public boolean isEnabled() {
        return enabled;
    }
}
spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>3.2.0</version>
        </dependency>
        
        <!-- 可选:配置处理器,生成配置元数据 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>3.2.0</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
使用自定义 Starter
<!-- 在其他项目中引入 -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
# application.yml
my:
  service:
    name: my-app
    enabled: true
    timeout: 5000
    retry-count: 5
@Service
public class AppService {
    
    private final MyService myService;
    
    public AppService(MyService myService) {
        this.myService = myService;
    }
    
    public void doSomething() {
        System.out.println(myService.sayHello());
    }
}

3.4 配置属性绑定示例

import org.springframework.boot.context.properties.*;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.*;

// 嵌套属性
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
    
    @NotBlank
    private String name;
    
    @Min(1)
    @Max(65535)
    private int port = 8080;
    
    private final Security security = new Security();
    private final Database database = new Database();
    private final Cache cache = new Cache();
    
    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }
    public Security getSecurity() { return security; }
    public Database getDatabase() { return database; }
    public Cache getCache() { return cache; }
    
    // 嵌套类
    public static class Security {
        private boolean enabled = true;
        private String secretKey;
        
        // getter/setter
        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        public String getSecretKey() { return secretKey; }
        public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
    }
    
    public static class Database {
        private String url;
        private String username;
        private String password;
        private int poolSize = 10;
        
        // getter/setter
        public String getUrl() { return url; }
        public void setUrl(String url) { this.url = url; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
        public int getPoolSize() { return poolSize; }
        public void setPoolSize(int poolSize) { this.poolSize = poolSize; }
    }
    
    public static class Cache {
        private boolean enabled = false;
        private String type = "redis";
        private long ttl = 3600;
        
        // getter/setter
        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        public String getType() { return type; }
        public void setType(String type) { this.type = type; }
        public long getTtl() { return ttl; }
        public void setTtl(long ttl) { this.ttl = ttl; }
    }
}

// 启用配置属性
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class PropertiesConfig {
}

// 使用配置属性
@Service
public class ConfigurableService {
    
    private final AppProperties properties;
    
    public ConfigurableService(AppProperties properties) {
        this.properties = properties;
    }
    
    public void printConfig() {
        System.out.println("应用名称: " + properties.getName());
        System.out.println("端口: " + properties.getPort());
        System.out.println("安全配置: enabled=" + properties.getSecurity().isEnabled());
        System.out.println("数据库URL: " + properties.getDatabase().getUrl());
        System.out.println("缓存类型: " + properties.getCache().getType());
    }
}
对应的配置文件
app:
  name: my-application
  port: 9090
  security:
    enabled: true
    secret-key: my-secret-key
  database:
    url: jdbc:mysql://localhost:3306/mydb
    username: root
    password: password
    pool-size: 20
  cache:
    enabled: true
    type: redis
    ttl: 7200

3.5 自动配置排除

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

// 方式1:通过注解排除
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// 方式2:通过配置文件排除
// application.yml
/*
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
*/

// 方式3:通过条件注解控制
@Configuration
@ConditionalOnProperty(prefix = "app.datasource", name = "enabled", havingValue = "true")
public class CustomDataSourceAutoConfiguration {
    // 只有当 app.datasource.enabled=true 时才加载
}

3.6 自动配置调试

import org.springframework.boot.autoconfigure.*;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;

@Configuration
public class AutoConfigDebug {
    
    // 打印已加载的自动配置
    @Bean
    public CommandLineRunner autoConfigReporter(AutoConfigurationReport report, Environment env) {
        return args -> {
            System.out.println("\n=== 自动配置报告 ===");
            System.out.println("调试模式: " + env.getProperty("debug"));
            
            report.getConditionAndOutcomesBySource().forEach((source, outcomes) -> {
                System.out.println("\n配置类: " + source);
                outcomes.forEach(outcome -> {
                    String status = outcome.getOutcome().isMatch() ? "✓ 匹配" : "✗ 不匹配";
                    System.out.println("  " + status + " - " + outcome.getOutcome().getMessage());
                });
            });
        };
    }
}

// 启用调试模式
// 方式1:启动参数
// java -jar app.jar --debug

// 方式2:配置文件
// debug: true

// 方式3:使用 actuator
// GET /actuator/conditions

四、实战应用场景

4.1 实现多数据源自动配置

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.*;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

// 多数据源属性
@ConfigurationProperties(prefix = "app.datasource")
public class MultiDataSourceProperties {
    
    private Map<String, DataSourceConfig> sources = new HashMap<>();
    
    public Map<String, DataSourceConfig> getSources() {
        return sources;
    }
    
    public static class DataSourceConfig {
        private String url;
        private String username;
        private String password;
        private String driverClassName;
        
        // getter/setter
        public String getUrl() { return url; }
        public void setUrl(String url) { this.url = url; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
        public String getDriverClassName() { return driverClassName; }
        public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; }
    }
}

// 多数据源自动配置
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(MultiDataSourceProperties.class)
public class MultiDataSourceAutoConfiguration {
    
    private final MultiDataSourceProperties properties;
    
    public MultiDataSourceAutoConfiguration(MultiDataSourceProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @Primary
    @ConditionalOnMissingBean
    public DataSource primaryDataSource() {
        MultiDataSourceProperties.DataSourceConfig config = 
            properties.getSources().get("primary");
        if (config == null) {
            throw new IllegalStateException("未配置主数据源");
        }
        return createDataSource(config);
    }
    
    @Bean
    @ConditionalOnProperty(prefix = "app.datasource.sources", name = "secondary")
    public DataSource secondaryDataSource() {
        MultiDataSourceProperties.DataSourceConfig config = 
            properties.getSources().get("secondary");
        return createDataSource(config);
    }
    
    private DataSource createDataSource(MultiDataSourceProperties.DataSourceConfig config) {
        // 创建数据源逻辑
        return null;
    }
}
配置示例
app:
  datasource:
    sources:
      primary:
        url: jdbc:mysql://localhost:3306/primary_db
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver
      secondary:
        url: jdbc:mysql://localhost:3306/secondary_db
        username: root
        password: password
        driver-class-name: com.mysql.cj.jdbc.Driver

4.2 实现缓存自动配置

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.*;
import org.springframework.context.annotation.*;

// 缓存属性
@ConfigurationProperties(prefix = "app.cache")
public class CacheProperties {
    
    private boolean enabled = true;
    private String type = "redis";
    private long defaultTtl = 3600;
    private int maxSize = 1000;
    
    // getter/setter
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    public long getDefaultTtl() { return defaultTtl; }
    public void setDefaultTtl(long defaultTtl) { this.defaultTtl = defaultTtl; }
    public int getMaxSize() { return maxSize; }
    public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
}

// 缓存接口
public interface CacheService {
    void put(String key, Object value);
    void put(String key, Object value, long ttl);
    Object get(String key);
    void delete(String key);
    void clear();
}

// Redis 缓存实现
public class RedisCacheService implements CacheService {
    private final long defaultTtl;
    
    public RedisCacheService(long defaultTtl) {
        this.defaultTtl = defaultTtl;
    }
    
    @Override
    public void put(String key, Object value) {
        put(key, value, defaultTtl);
    }
    
    @Override
    public void put(String key, Object value, long ttl) {
        System.out.println("Redis 缓存写入: " + key);
    }
    
    @Override
    public Object get(String key) {
        System.out.println("Redis 缓存读取: " + key);
        return null;
    }
    
    @Override
    public void delete(String key) {
        System.out.println("Redis 缓存删除: " + key);
    }
    
    @Override
    public void clear() {
        System.out.println("Redis 缓存清空");
    }
}

// 本地缓存实现
public class LocalCacheService implements CacheService {
    private final int maxSize;
    private final Map<String, Object> cache = new LinkedHashMap<String, Object>(16, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
            return size() > maxSize;
        }
    };
    
    public LocalCacheService(int maxSize) {
        this.maxSize = maxSize;
    }
    
    @Override
    public void put(String key, Object value) {
        cache.put(key, value);
    }
    
    @Override
    public void put(String key, Object value, long ttl) {
        cache.put(key, value);
    }
    
    @Override
    public Object get(String key) {
        return cache.get(key);
    }
    
    @Override
    public void delete(String key) {
        cache.remove(key);
    }
    
    @Override
    public void clear() {
        cache.clear();
    }
}

// 自动配置类
@Configuration
@ConditionalOnClass(CacheService.class)
@EnableConfigurationProperties(CacheProperties.class)
public class CacheAutoConfiguration {
    
    private final CacheProperties properties;
    
    public CacheAutoConfiguration(CacheProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
    @ConditionalOnClass(name = "redis.clients.jedis.Jedis")
    public CacheService redisCacheService() {
        return new RedisCacheService(properties.getDefaultTtl());
    }
    
    @Bean
    @ConditionalOnMissingBean(CacheService.class)
    @ConditionalOnProperty(prefix = "app.cache", name = "enabled", havingValue = "true", matchIfMissing = true)
    public CacheService localCacheService() {
        return new LocalCacheService(properties.getMaxSize());
    }
}

4.3 实现日志自动配置

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.*;
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Value;

// 日志属性
@ConfigurationProperties(prefix = "app.logging")
public class LoggingProperties {
    
    private boolean enabled = true;
    private String level = "INFO";
    private boolean includeRequest = true;
    private boolean includeResponse = true;
    private int maxPayloadLength = 1000;
    
    // getter/setter
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public String getLevel() { return level; }
    public void setLevel(String level) { this.level = level; }
    public boolean isIncludeRequest() { return includeRequest; }
    public void setIncludeRequest(boolean includeRequest) { this.includeRequest = includeRequest; }
    public boolean isIncludeResponse() { return includeResponse; }
    public void setIncludeResponse(boolean includeResponse) { this.includeResponse = includeResponse; }
    public int getMaxPayloadLength() { return maxPayloadLength; }
    public void setMaxPayloadLength(int maxPayloadLength) { this.maxPayloadLength = maxPayloadLength; }
}

// 日志服务
public class LoggingService {
    
    private final LoggingProperties properties;
    
    public LoggingService(LoggingProperties properties) {
        this.properties = properties;
    }
    
    public void logRequest(String method, String uri, String body) {
        if (!properties.isEnabled() || !properties.isIncludeRequest()) {
            return;
        }
        
        String truncatedBody = truncate(body, properties.getMaxPayloadLength());
        System.out.println(String.format("[%s] %s %s", properties.getLevel(), method, uri));
        if (body != null) {
            System.out.println("Request Body: " + truncatedBody);
        }
    }
    
    public void logResponse(int status, String body) {
        if (!properties.isEnabled() || !properties.isIncludeResponse()) {
            return;
        }
        
        String truncatedBody = truncate(body, properties.getMaxPayloadLength());
        System.out.println("Response Status: " + status);
        System.out.println("Response Body: " + truncatedBody);
    }
    
    private String truncate(String str, int maxLength) {
        if (str == null) return null;
        if (str.length() <= maxLength) return str;
        return str.substring(0, maxLength) + "...";
    }
}

// 自动配置类
@Configuration
@ConditionalOnClass(LoggingService.class)
@EnableConfigurationProperties(LoggingProperties.class)
public class LoggingAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "app.logging", name = "enabled", havingValue = "true", matchIfMissing = true)
    public LoggingService loggingService(LoggingProperties properties) {
        return new LoggingService(properties);
    }
    
    @Bean
    @ConditionalOnBean(LoggingService.class)
    @ConditionalOnWebApplication
    public LoggingFilter loggingFilter(LoggingService loggingService) {
        return new LoggingFilter(loggingService);
    }
}

// 日志过滤器
public class LoggingFilter implements jakarta.servlet.Filter {
    
    private final LoggingService loggingService;
    
    public LoggingFilter(LoggingService loggingService) {
        this.loggingService = loggingService;
    }
    
    @Override
    public void doFilter(jakarta.servlet.ServletRequest request, 
                        jakarta.servlet.ServletResponse response, 
                        jakarta.servlet.FilterChain chain) 
            throws IOException, jakarta.servlet.ServletException {
        
        jakarta.servlet.http.HttpServletRequest httpRequest = 
            (jakarta.servlet.http.HttpServletRequest) request;
        
        loggingService.logRequest(
            httpRequest.getMethod(), 
            httpRequest.getRequestURI(), 
            null
        );
        
        chain.doFilter(request, response);
    }
}

五、总结与最佳实践

自动配置开发步骤

  1. 创建自动配置类

    • 使用 @Configuration 标记
    • 添加条件注解
    • 定义 Bean
  2. 创建属性类

    • 使用 @ConfigurationProperties
    • 定义可配置属性
    • 提供默认值
  3. 注册自动配置

    • 创建 spring.factories
    • 添加 EnableAutoConfiguration 配置
  4. 创建 Starter 模块

    • 管理依赖
    • 提供文档

最佳实践

  1. 条件注解使用

    • 使用最合适的条件注解
    • 组合条件时注意顺序
    • 提供 matchIfMissing 默认值
  2. 属性设计

    • 提供合理的默认值
    • 使用嵌套类组织属性
    • 添加文档注释
  3. Bean 定义

    • 使用 @ConditionalOnMissingBean 允许覆盖
    • 考虑 Bean 的创建顺序
    • 避免循环依赖
  4. 文档和测试

    • 提供配置元数据
    • 编写自动配置测试
    • 提供使用文档

Spring Boot 自动配置是简化 Spring 应用开发的核心机制。理解其原理和最佳实践,能够帮助开发者更好地使用 Spring Boot,并能开发出高质量的 Starter 组件。