Java配置类完全指南笔记


📖 目录

  1. 什么是配置类
  2. 为什么需要配置类
  3. 配置类核心注解详解
  4. 配置类执行时机
  5. 完整实战案例
  6. 配置类高级用法
  7. 最佳实践
  8. 常见误区

一、什么是配置类?

1.1 核心定义

配置类 = 用来配置和组装 Spring 应用的特殊 Java 类

特点:
├─ 使用 @Configuration 注解标记
├─ 包含 @Bean 方法来创建对象
├─ 在应用启动时执行
├─ 负责搭建应用的基础设施
└─ 执行一次后,创建的对象会一直存在

1.2 形象比喻 🏗️

比喻1:工厂的建设过程
传统工厂建设:
────────────────────────────────
工人手动组装每个零件
每次生产都要重新搭建
效率低,容易出错


配置类建设:
────────────────────────────────
@Configuration = 工厂建设图纸
@Bean = 自动化生产线

启动时:
├─ 按图纸建好整个工厂
├─ 安装所有生产线
├─ 配置好所有设备
└─ 工厂就绪,随时生产

运行时:
├─ 订单来了,直接生产
├─ 不需要重新建工厂
└─ 高效、稳定
比喻2:餐厅的开业准备
没有配置类(混乱的餐厅):
────────────────────────────────
每次客人来:
├─ 现场购买食材
├─ 临时招聘厨师
├─ 现场布置餐桌
└─ 效率极低


有配置类(专业餐厅):
────────────────────────────────
@Configuration = 餐厅装修方案
@Bean = 厨房设备、餐桌、厨师团队

开业前(启动时):
├─ 装修好餐厅
├─ 安装好厨房设备
├─ 招聘好厨师团队
├─ 摆放好餐桌椅
└─ 准备好食材供应商

开业后(运行时):
├─ 客人点餐
├─ 直接使用已有设备和团队
├─ 快速出餐
└─ 不需要重新装修

1.3 代码对比

// ========== 没有配置类(原始方式)==========
public class UserService {
    public User getUser(Long id) {
        // 每次都要手动创建依赖
        Connection conn = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/db", "root", "123456");
        PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
        ps.setLong(1, id);
        ResultSet rs = ps.executeQuery();
        
        User user = new User();
        if (rs.next()) {
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
        }
        
        rs.close();
        ps.close();
        conn.close();
        
        return user;
    }
}

问题:
├─ 每次查询都创建连接(效率低)
├─ 数据库地址硬编码(难维护)
├─ 资源管理复杂(容易忘记关闭)
└─ 重复代码多(100个方法写100遍)


// ========== 有配置类(现代方式)==========
// 1. 配置类(启动时执行一次)
@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource dataSource() {
        // 创建数据库连接池(只创建一次)
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setMaximumPoolSize(10);  // 10个连接
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        // 创建数据库操作工具(只创建一次)
        return new JdbcTemplate(dataSource);
    }
}

// 2. 业务代码(简洁明了)
@Service
public class UserService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;  // Spring自动注入
    
    public User getUser(Long id) {
        // 直接使用,不需要关心连接管理
        return jdbcTemplate.queryForObject(
            "SELECT * FROM user WHERE id = ?",
            new BeanPropertyRowMapper<>(User.class),
            id
        );
    }
}

优势:
├─ 连接池只创建一次(高效)
├─ 配置集中管理(易维护)
├─ 自动资源管理(不会忘记关闭)
└─ 业务代码简洁(专注业务逻辑)

二、为什么需要配置类?

2.1 核心价值

1. 依赖注入(Dependency Injection)
   └─ 对象之间的依赖由 Spring 管理,不需要手动 new

2. 解耦(Decoupling)
   └─ 业务代码不需要知道对象如何创建

3. 集中管理(Centralized Management)
   └─ 所有配置在一个地方,修改方便

4. 生命周期管理(Lifecycle Management)
   └─ Spring 自动管理对象的创建和销毁

5. 测试友好(Testability)
   └─ 可以轻松替换依赖进行测试

2.2 实际问题场景

场景1:数据库连接管理
// ❌ 没有配置类的灾难
public class OrderService {
    public void createOrder(Order order) {
        // 每次都创建新连接
        Connection conn = DriverManager.getConnection(...);
        // 使用连接
        conn.close();
    }
}

public class UserService {
    public void updateUser(User user) {
        // 又创建一次连接
        Connection conn = DriverManager.getConnection(...);
        // 使用连接
        conn.close();
    }
}

public class ProductService {
    public void addProduct(Product product) {
        // 再创建一次连接
        Connection conn = DriverManager.getConnection(...);
        // 使用连接
        conn.close();
    }
}

问题统计:
├─ 100Service = 创建100次连接
├─ 1000个请求 = 创建100,000次连接
├─ 数据库崩溃
└─ 应用卡死


// ✅ 有配置类的优雅方案
@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource dataSource() {
        // 启动时创建一次连接池
        HikariDataSource ds = new HikariDataSource();
        ds.setMaximumPoolSize(20);  // 最多20个连接
        return ds;
    }
}

@Service
public class OrderService {
    @Autowired
    private DataSource dataSource;  // 共享同一个连接池
    
    public void createOrder(Order order) {
        // 从连接池获取,用完归还
        try (Connection conn = dataSource.getConnection()) {
            // 使用连接
        }
    }
}

效果:
├─ 100Service = 共享1个连接池
├─ 1000个请求 = 最多用20个连接
├─ 数据库稳定
└─ 应用高效
场景2:多环境配置切换
// ❌ 没有配置类的痛苦
public class EmailService {
    public void sendEmail(String to, String content) {
        // 硬编码:开发环境
        String host = "dev-smtp.example.com";
        int port = 25;
        
        // 部署到生产环境时:
        // 需要改代码!改完重新编译!
        // String host = "prod-smtp.example.com";
        // int port = 587;
        
        // 发送邮件...
    }
}

切换环境的步骤:
1. 找到所有硬编码的地方(可能有50处)
2. 一个个修改
3. 重新编译
4. 重新打包
5. 重新部署
6. 祈祷没有改漏


// ✅ 有配置类的轻松切换
// application-dev.yml(开发环境)
email:
  host: dev-smtp.example.com
  port: 25

// application-prod.yml(生产环境)
email:
  host: prod-smtp.example.com
  port: 587

// 配置类
@Configuration
public class EmailConfig {
    
    @Value("${email.host}")
    private String host;
    
    @Value("${email.port}")
    private int port;
    
    @Bean
    public EmailSender emailSender() {
        return new EmailSender(host, port);
    }
}

// 业务代码
@Service
public class EmailService {
    @Autowired
    private EmailSender emailSender;  // 自动适配环境
    
    public void sendEmail(String to, String content) {
        emailSender.send(to, content);
    }
}

切换环境的步骤:
1. 修改启动参数:--spring.profiles.active=prod
2. 完成!代码一行不改!

三、配置类核心注解详解

配置类本身会自动加载,类会被实例化,但类本身不会"执行"。普通方法不会被执行

组件类型是否自动执行说明
@AutoConfiguration✅ 自动加载类会被实例化
@Bean 方法✅ 自动执行返回值注册到容器
@Service/@Component✅ 自动加载单独的服务类
普通方法❌ 不自动执行需要被调用
@Resource/@Autowired✅ 自动注入依赖注入

3.1 @Configuration

/**
 * @Configuration:标记这是一个配置类
 * 
 * 作用:
 * 1. 告诉Spring:这个类用来配置Bean
 * 2. Spring会在启动时扫描并执行这个类
 * 3. 类中的 @Bean 方法会被调用
 */

@Configuration
public class AppConfig {
    // 配置内容
}

// 等价于传统的 XML 配置:
// <beans>
//     <bean id="..." class="..."/>
// </beans>

使用场景

// ✅ 推荐:每个功能模块一个配置类
@Configuration
public class DatabaseConfig {
    // 数据库相关配置
}

@Configuration
public class SecurityConfig {
    // 安全相关配置
}

@Configuration
public class RedisConfig {
    // Redis相关配置
}

// ❌ 不推荐:所有配置混在一起
@Configuration
public class AllConfig {
    // 数据库 + 安全 + Redis + ... 全在一起
    // 难以维护,难以理解
}

3.2 @Bean

/**
 * @Bean:创建一个由Spring管理的对象
 * 
 * 特点:
 * 1. 方法返回的对象会被放入Spring容器
 * 2. 默认情况下,Bean的名称就是方法名
 * 3. 方法只在启动时执行一次(单例)
 * 4. 可以通过 @Autowired 注入到其他地方
 */

@Configuration
public class DatabaseConfig {
    
    @Bean  // Bean的名称是 "dataSource"
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/db");
        return ds;  // 这个对象会被Spring管理
    }
    
    @Bean(name = "myDb")  // 自定义Bean名称
    public DataSource customDataSource() {
        return new HikariDataSource();
    }
}

@Bean 的执行过程

启动时发生的事:
════════════════════════════════════════

1. Spring 扫描到 @Configuration2. Spring 调用 @Bean 方法
   DataSource ds = dataSource();  // 执行方法
   
3. Spring 把返回的对象存起来
   容器.put("dataSource", ds);
   
4. 其他地方可以获取这个对象
   @Autowired
   private DataSource dataSource;  // Spring自动注入


运行时:
════════════════════════════════════════
@Bean 方法不会再执行
但创建的对象一直存在
所有地方共享这一个对象

3.3 @Autowired(依赖注入)

/**
 * @Autowired:自动注入依赖
 * 
 * 作用:
 * 1. 告诉Spring:这里需要一个对象
 * 2. Spring会从容器中找到匹配的对象
 * 3. 自动注入进来
 */

@Service
public class UserService {
    
    // 方式1:字段注入(最常用,但不推荐)
    @Autowired
    private UserRepository userRepository;
    
    // 方式2:构造器注入(推荐)
    private final UserRepository userRepository;
    
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // 方式3:Setter注入
    private UserRepository userRepository;
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

依赖注入的形象比喻 🔌:

比喻:电器插座系统
────────────────────────────────────

传统方式(手动new):
├─ 电器:我需要电
├─ 你:好的,我去发电厂买电
├─ 你:自己拉电线到家里
├─ 你:连接到电器上
└─ 电器:终于能用了...

配置类 + 依赖注入:
├─ 发电厂(@Configuration):建好了,发电中
├─ 插座(Spring容器):到处都有
├─ 电器(@Autowired):插上就能用
└─ 你:啥都不用管


代码对应:
────────────────────────────────────
// 发电厂(配置类)
@Configuration
public class PowerConfig {
    @Bean
    public Electricity electricity() {
        return new Electricity(220, "AC");  // 发电
    }
}

// 插座(Spring容器)
[Spring Container]
├─ electricity (Electricity对象)
└─ 等待被注入

// 电器(业务代码)
@Component
public class Television {
    @Autowired
    private Electricity electricity;  // 插上插座
    
    public void turnOn() {
        electricity.use();  // 直接用电
    }
}

3.4 @Value(读取配置)

/**
 * @Value:从配置文件读取值
 * 
 * 用途:
 * 1. 读取 application.yml 的配置
 * 2. 读取环境变量
 * 3. 使用 SpEL 表达式
 */

// application.yml
app:
  name: MyApp
  version: 1.0.0
  max-connections: 100

@Component
public class AppInfo {
    
    // 读取配置值
    @Value("${app.name}")
    private String appName;  // "MyApp"
    
    @Value("${app.version}")
    private String version;  // "1.0.0"
    
    // 设置默认值(如果配置不存在)
    @Value("${app.timeout:30}")
    private int timeout;  // 如果没配置,默认30
    
    // 读取环境变量
    @Value("${JAVA_HOME}")
    private String javaHome;
    
    // SpEL表达式
    @Value("#{${app.max-connections} * 2}")
    private int doubleConnections;  // 200
}

3.5 @ConfigurationProperties(批量读取)

/**
 * @ConfigurationProperties:批量读取配置
 * 
 * 优势:
 * 1. 类型安全
 * 2. 支持复杂对象
 * 3. IDE有提示
 * 4. 统一管理
 */

// application.yml
email:
  host: smtp.example.com
  port: 587
  username: admin@example.com
  password: secret123
  properties:
    mail.smtp.auth: true
    mail.smtp.starttls.enable: true

// 配置类
@Data
@Component
@ConfigurationProperties(prefix = "email")
public class EmailProperties {
    private String host;
    private int port;
    private String username;
    private String password;
    private Map<String, String> properties;
}

// 使用
@Configuration
public class EmailConfig {
    
    @Autowired
    private EmailProperties emailProperties;
    
    @Bean
    public JavaMailSender mailSender() {
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        sender.setHost(emailProperties.getHost());
        sender.setPort(emailProperties.getPort());
        sender.setUsername(emailProperties.getUsername());
        sender.setPassword(emailProperties.getPassword());
        sender.setJavaMailProperties(
            convertToProperties(emailProperties.getProperties())
        );
        return sender;
    }
}

四、配置类执行时机

4.1 完整生命周期

应用启动的详细流程:
════════════════════════════════════════════════════════

0秒 - main() 方法执行
│
├─ SpringApplication.run(App.class, args);
│
├─ ▼ 阶段1:准备环境
│  ├─ 读取 application.yml
│  ├─ 读取环境变量
│  └─ 合并配置
│
├─ ▼ 阶段2:创建ApplicationContext(Spring容器)
│  └─ 准备一个空容器
│
├─ ▼ 阶段3:扫描组件
│  ├─ 扫描 @Configuration 类
│  ├─ 扫描 @Component、@Service 类
│  └─ 扫描 @Controller 类
│
├─ ▼ 阶段4:【配置类执行】← 关键时刻
│  │
│  ├─ 4.1 实例化配置类
│  │   new DatabaseConfig()
│  │   new SecurityConfig()
│  │   new RedisConfig()
│  │
│  ├─ 4.2 处理 @Bean 方法
│  │   ├─ 调用 dataSource() 方法
│  │   │   └─ 创建 DataSource 对象
│  │   │   └─ 存入容器: 容器.put("dataSource", ds)
│  │   │
│  │   ├─ 调用 jdbcTemplate() 方法
│  │   │   └─ 创建 JdbcTemplate 对象
│  │   │   └─ 存入容器
│  │   │
│  │   └─ 调用 filterChain() 方法
│  │       └─ 创建 SecurityFilterChain 对象
│  │       └─ 存入容器
│  │
│  └─ 4.3 处理依赖注入
│      ├─ UserService 需要 UserRepository
│      │   └─ 从容器获取 UserRepository
│      │   └─ 注入到 UserService
│      │
│      └─ OrderService 需要 OrderRepository
│          └─ 从容器获取 OrderRepository
│          └─ 注入到 OrderService
│
├─ ▼ 阶段5:启动Web服务器
│  ├─ 启动 Tomcat(默认8080端口)
│  └─ 注册所有 Controller 的路由
│
└─ ▼ 阶段6:启动完成
   └─ 日志输出:Started Application in 2.5 seconds

════════════════════════════════════════════════════════
配置类工作结束
创建的对象常驻内存
开始接受用户请求
════════════════════════════════════════════════════════

4.2 可视化时间轴

时间    |  事件                              |  内存状态
───────────────────────────────────────────────────────────
0.0s   |  应用启动                          |0.1s   |  读取配置文件                      |  配置数据加载
0.3s   |  扫描到配置类                      |  DatabaseConfig.class
       |  DatabaseConfig                    |  SecurityConfig.class
       |  SecurityConfig                    |
       |  RedisConfig                       |
       |                                     |
0.5s   |  【执行配置类】                     |  开始创建Bean
       |  ├─ new DatabaseConfig()           |
       |  ├─ dataSource() 方法执行          |  ├─ DataSource对象
       |  │   └─ 创建HikariDataSource      ||  ├─ jdbcTemplate() 方法执行       |  ├─ JdbcTemplate对象
       |  │   └─ 创建JdbcTemplate          ||  └─ ...                            |  └─ ...
       |                                     |
1.0s   |  所有Bean创建完成                  |  Spring容器已满
       |                                     |  ├─ dataSource
       |                                     |  ├─ jdbcTemplate
       |                                     |  ├─ userService
       |                                     |  ├─ orderService
       |                                     |  └─ ...
       |                                     |
1.5s   |  Tomcat启动                        |  监听8080端口
       |                                     |
2.0s   |  应用启动完成                      |  等待请求
       |                                     |
───────────────────────────────────────────────────────────
       |  【配置类不再执行】                |
       |Bean对象一直存在】              |
───────────────────────────────────────────────────────────
2.5s   |  用户请求1到达                     |  使用已有Bean
       |  GET /api/users                    |  ├─ 从容器获取
       |  ├─ 进入过滤器链                  |  │   userService
       |  ├─ 调用UserController            |  └─ 执行业务逻辑
       |  └─ 调用UserService               |
       |      └─ 使用 jdbcTemplate         |  (配置类不执行)
       |                                     |
3.0s   |  用户请求2到达                     |  使用已有Bean
       |  POST /api/orders                  |  (还是那些对象)
       |  └─ 使用 OrderService             |
       |      └─ 使用 jdbcTemplate         |  (配置类不执行)
       |                                     |
4.0s   |  用户请求3到达                     |  使用已有Bean
       |  ...                               |  (配置类不执行)

4.3 代码验证

@Configuration
public class DemoConfig {
    
    public DemoConfig() {
        System.out.println("========================================");
        System.out.println("DemoConfig 构造函数执行");
        System.out.println("时间:" + LocalDateTime.now());
        System.out.println("========================================");
    }
    
    @Bean
    public DataSource dataSource() {
        System.out.println(">>> dataSource() 方法执行");
        System.out.println(">>> 时间:" + LocalDateTime.now());
        System.out.println(">>> 创建DataSource对象");
        
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/db");
        
        System.out.println(">>> DataSource创建完成");
        return ds;
    }
}

@Service
public class UserService {
    
    @Autowired
    private DataSource dataSource;
    
    public UserService() {
        System.out.println(">>> UserService 构造函数执行");
        System.out.println(">>> 时间:" + LocalDateTime.now());
    }
    
    @PostConstruct
    public void init() {
        System.out.println(">>> UserService 依赖注入完成");
        System.out.println(">>> dataSource 是否为null? " + (dataSource == null));
    }
    
    public User getUser(Long id) {
        System.out.println("▶▶▶ getUser() 方法执行(处理用户请求)");
        System.out.println("▶▶▶ 时间:" + LocalDateTime.now());
        // 业务逻辑...
    }
}

// 运行输出:
========================================
DemoConfig 构造函数执行
时间:2024-10-27T10:00:00.500
========================================
>>> dataSource() 方法执行
>>> 时间:2024-10-27T10:00:00.520
>>> 创建DataSource对象
>>> DataSource创建完成
>>> UserService 构造函数执行
>>> 时间:2024-10-27T10:00:00.800
>>> UserService 依赖注入完成
>>> dataSource 是否为null? false
[启动完成,等待请求...]

[用户请求到达]
▶▶▶ getUser() 方法执行(处理用户请求)
▶▶▶ 时间:2024-10-27T10:01:30.000

[又一个用户请求]
▶▶▶ getUser() 方法执行(处理用户请求)
▶▶▶ 时间:2024-10-27T10:02:45.000

// 注意:配置类和@Bean方法只执行一次(启动时)
// 但getUser()每次请求都执行

五、完整实战案例

案例1:电商系统的配置

5.1 项目结构
src/main/java/com/example/shop/
├── config/                    # 配置包
│   ├── DatabaseConfig.java    # 数据库配置
│   ├── RedisConfig.java       # Redis配置
│   ├── SecurityConfig.java    # 安全配置
│   └── EmailConfig.java       # 邮件配置
│
├── controller/                # 控制器
│   ├── UserController.java
│   ├── ProductController.java
│   └── OrderController.java
│
├── service/                   # 服务层
│   ├── UserService.java
│   ├── ProductService.java
│   └── OrderService.java
│
└── ShopApplication.java       # 启动类

src/main/resources/
├── application.yml            # 主配置
├── application-dev.yml        # 开发环境
└── application-prod.yml       # 生产环境
5.2 数据库配置
# application.yml(主配置)
spring:
  profiles:
    active: dev  # 激活开发环境

# application-dev.yml(开发环境)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/shop_dev
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10        # 开发环境10个连接够了
      minimum-idle: 5
      connection-timeout: 20000
      idle-timeout: 300000

# application-prod.yml(生产环境)
spring:
  datasource:
    url: jdbc:mysql://prod-db.example.com:3306/shop_prod
    username: shop_user
    password: ${DB_PASSWORD}       # 从环境变量读取
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 50        # 生产环境需要更多连接
      minimum-idle: 20
      connection-timeout: 30000
      idle-timeout: 600000
/**
 * 数据库配置类
 * 
 * 作用:配置数据源和事务管理
 */
@Configuration
@EnableTransactionManagement  // 启用事务
public class DatabaseConfig {
    
    @Value("${spring.datasource.url}")
    private String jdbcUrl;
    
    @Value("${spring.datasource.username}")
    private String username;
    
    @Value("${spring.datasource.password}")
    private String password;
    
    @Value("${spring.datasource.hikari.maximum-pool-size}")
    private int maxPoolSize;
    
    /**
     * 数据源Bean
     * 
     * 说明:
     * - 使用HikariCP连接池(性能最好)
     * - 启动时创建一次
     * - 所有Service共享这一个连接池
     */
    @Bean
    public DataSource dataSource() {
        System.out.println(">>> 创建数据源...");
        System.out.println(">>> JDBC URL: " + jdbcUrl);
        System.out.println(">>> 最大连接数: " + maxPoolSize);
        
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaximumPoolSize(maxPoolSize);
        dataSource.setMinimumIdle(5);
        dataSource.setConnectionTimeout(20000);
        
        // 可选:设置连接测试查询
        dataSource.setConnectionTestQuery("SELECT 1");
        
        System.out.println(">>> 数据源创建完成!");
        return dataSource;
    }
    
    /**
     * JdbcTemplate Bean
     * 
     * 说明:
     * - 依赖 dataSource
     * - Spring会自动注入上面创建的dataSource
     */
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        System.out.println(">>> 创建JdbcTemplate...");
        return new JdbcTemplate(dataSource);
    }
    
    /**
     * 事务管理器
     * 
     * 说明:
     * - 管理数据库事务
     * - 配合 @Transactional 注解使用
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
5.3 Redis配置
# application-dev.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 2

# application-prod.yml
spring:
  redis:
    host: prod-redis.example.com
    port: 6379
    password: ${REDIS_PASSWORD}
    database: 0
    timeout: 5000
    lettuce:
      pool:
        max-active: 50
        max-idle: 20
        min-idle: 10
/**
 * Redis配置类
 * 
 * 作用:配置Redis连接和序列化方式
 */
@Configuration
public class RedisConfig {
    
    /**
     * Redis连接工厂
     * 
     * 说明:
     * - Spring Boot已经自动配置了
     * - 这里只是展示如何自定义
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(
            @Value("${spring.redis.host}") String host,
            @Value("${spring.redis.port}") int port,
            @Value("${spring.redis.password}") String password) {
        
        System.out.println(">>> 创建Redis连接工厂...");
        System.out.println(">>> Redis Host: " + host + ":" + port);
        
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        if (password != null && !password.isEmpty()) {
            config.setPassword(password);
        }
        
        return new LettuceConnectionFactory(config);
    }
    
    /**
     * RedisTemplate配置
     * 
     * 说明:
     * - 配置Key和Value的序列化方式
     * - Key用String序列化
     * - Value用JSON序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        
        System.out.println(">>> 创建RedisTemplate...");
        
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // Key序列化:使用String
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        template.setKeySerializer(keySerializer);
        template.setHashKeySerializer(keySerializer);
        
        // Value序列化:使用JSON
        Jackson2JsonRedisSerializer<Object> valueSerializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        template.setValueSerializer(valueSerializer);
        template.setHashValueSerializer(valueSerializer);
        
        template.afterPropertiesSet();
        
        System.out.println(">>> RedisTemplate创建完成!");
        return template;
    }
    
    /**
     * 缓存管理器
     * 
     * 说明:
     * - 配合 @Cacheable 注解使用
     * - 自动管理缓存
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))  // 默认1小时过期
            .disableCachingNullValues();    // 不缓存null值
        
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
    }
}
5.4 Security配置(Token认证)
# application.yml
security:
  token:
    header: Authorization
    secret: ${JWT_SECRET:my-dev-secret-key-123456}
    expire-time: 7200     # 2小时(秒)
    refresh-expire-time: 2592000  # 30天(秒)
  permit-all-urls:
    - /api/auth/login
    - /api/auth/register
    - /api/products/**    # 商品列表公开访问
    - /doc.html
    - /swagger-ui/**
/**
 * Security配置类
 * 
 * 作用:配置Spring Security的安全策略
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Autowired
    private TokenAuthenticationFilter tokenAuthenticationFilter;
    
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    
    @Value("${security.permit-all-urls}")
    private List<String> permitAllUrls;
    
    /**
     * 密码加密器
     * 
     * 说明:
     * - 使用BCrypt算法
     * - 注册时加密密码
     * - 登录时验证密码
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        System.out.println(">>> 创建密码加密器(BCrypt)");
        return new BCryptPasswordEncoder();
    }
    
    /**
     * 认证管理器
     * 
     * 说明:
     * - 负责用户认证
     * - 登录时使用
     */
    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
    
    /**
     * 安全过滤器链
     * 
     * 说明:
     * - 这是配置的核心
     * - 定义哪些URL需要认证
     * - 定义Token过滤器的位置
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        System.out.println(">>> 配置Spring Security过滤器链...");
        System.out.println(">>> 白名单URL: " + permitAllUrls);
        
        return http
                // 1. 禁用CSRF(使用Token,不需要CSRF防护)
                .csrf(csrf -> csrf.disable())
                
                // 2. 禁用Session(无状态认证)
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                
                // 3. 配置跨域
                .cors(Customizer.withDefaults())
                
                // 4. 配置URL访问规则
                .authorizeHttpRequests(auth -> auth
                        // 白名单:允许匿名访问
                        .requestMatchers(permitAllUrls.toArray(new String[0]))
                            .permitAll()
                        // 静态资源:允许匿名访问
                        .requestMatchers("/", "/*.html", "/*.css", "/*.js")
                            .permitAll()
                        // 管理员接口:需要ADMIN角色
                        .requestMatchers("/api/admin/**")
                            .hasRole("ADMIN")
                        // 其他请求:需要认证
                        .anyRequest()
                            .authenticated()
                )
                
                // 5. 添加Token验证过滤器
                .addFilterBefore(
                        tokenAuthenticationFilter,
                        UsernamePasswordAuthenticationFilter.class
                )
                
                // 6. 异常处理
                .exceptionHandling(ex -> ex
                        // 未认证时的处理
                        .authenticationEntryPoint(authenticationEntryPoint)
                        // 无权限时的处理
                        .accessDeniedHandler(accessDeniedHandler)
                )
                
                .build();
    }
}
5.5 邮件配置
# application.yml
email:
  host: smtp.gmail.com
  port: 587
  username: noreply@shop.com
  password: ${EMAIL_PASSWORD}
  protocol: smtp
  default-encoding: UTF-8
  properties:
    mail.smtp.auth: true
    mail.smtp.starttls.enable: true
    mail.smtp.starttls.required: true
/**
 * 邮件配置属性
 */
@Data
@Component
@ConfigurationProperties(prefix = "email")
public class EmailProperties {
    private String host;
    private int port;
    private String username;
    private String password;
    private String protocol = "smtp";
    private String defaultEncoding = "UTF-8";
    private Map<String, String> properties = new HashMap<>();
}

/**
 * 邮件配置类
 */
@Configuration
public class EmailConfig {
    
    @Autowired
    private EmailProperties emailProperties;
    
    /**
     * 邮件发送器
     * 
     * 说明:
     * - 配置SMTP服务器
     * - 支持发送HTML邮件
     */
    @Bean
    public JavaMailSender mailSender() {
        System.out.println(">>> 创建邮件发送器...");
        System.out.println(">>> SMTP服务器: " + emailProperties.getHost());
        
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        sender.setHost(emailProperties.getHost());
        sender.setPort(emailProperties.getPort());
        sender.setUsername(emailProperties.getUsername());
        sender.setPassword(emailProperties.getPassword());
        sender.setProtocol(emailProperties.getProtocol());
        sender.setDefaultEncoding(emailProperties.getDefaultEncoding());
        
        // 设置SMTP属性
        Properties props = new Properties();
        emailProperties.getProperties().forEach(props::setProperty);
        sender.setJavaMailProperties(props);
        
        System.out.println(">>> 邮件发送器创建完成!");
        return sender;
    }
}
5.6 业务代码使用配置
/**
 * 用户服务
 * 
 * 说明:
 * - 使用配置类创建的Bean
 * - 不需要关心这些Bean是如何创建的
 * - 专注业务逻辑
 */
@Service
@Transactional
public class UserService {
    
    // 所有依赖都自动注入
    @Autowired
    private JdbcTemplate jdbcTemplate;  // 来自DatabaseConfig
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;  // 来自RedisConfig
    
    @Autowired
    private PasswordEncoder passwordEncoder;  // 来自SecurityConfig
    
    @Autowired
    private JavaMailSender mailSender;  // 来自EmailConfig
    
    /**
     * 用户注册
     */
    public User register(RegisterDTO dto) {
        // 1. 加密密码(使用配置的passwordEncoder)
        String encodedPassword = passwordEncoder.encode(dto.getPassword());
        
        // 2. 保存到数据库(使用配置的jdbcTemplate)
        String sql = "INSERT INTO user (username, password, email) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, dto.getUsername(), encodedPassword, dto.getEmail());
        
        // 3. 缓存用户信息(使用配置的redisTemplate)
        User user = getUserByUsername(dto.getUsername());
        String cacheKey = "user:" + user.getId();
        redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
        
        // 4. 发送欢迎邮件(使用配置的mailSender)
        sendWelcomeEmail(user.getEmail());
        
        return user;
    }
    
    /**
     * 用户登录
     */
    public String login(LoginDTO dto) {
        // 1. 查询用户
        User user = getUserByUsername(dto.getUsername());
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        
        // 2. 验证密码(使用配置的passwordEncoder)
        if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
            throw new BusinessException("密码错误");
        }
        
        // 3. 生成Token
        String token = generateToken(user);
        
        // 4. 缓存Token(使用配置的redisTemplate)
        String tokenKey = "token:" + token;
        redisTemplate.opsForValue().set(tokenKey, user.getId(), 2, TimeUnit.HOURS);
        
        return token;
    }
    
    /**
     * 获取用户信息
     */
    public User getUserById(Long id) {
        // 1. 先从缓存获取(使用配置的redisTemplate)
        String cacheKey = "user:" + id;
        User user = (User) redisTemplate.opsForValue().get(cacheKey);
        
        if (user != null) {
            System.out.println(">>> 从缓存获取用户: " + id);
            return user;
        }
        
        // 2. 缓存未命中,从数据库查询(使用配置的jdbcTemplate)
        System.out.println(">>> 从数据库查询用户: " + id);
        String sql = "SELECT * FROM user WHERE id = ?";
        user = jdbcTemplate.queryForObject(
            sql,
            new BeanPropertyRowMapper<>(User.class),
            id
        );
        
        // 3. 写入缓存
        if (user != null) {
            redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);
        }
        
        return user;
    }
    
    private void sendWelcomeEmail(String to) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(to);
        message.setSubject("欢迎注册我们的商城");
        message.setText("感谢您的注册!");
        mailSender.send(message);
    }
    
    private String generateToken(User user) {
        // JWT生成逻辑...
        return "token-" + user.getId();
    }
    
    private User getUserByUsername(String username) {
        String sql = "SELECT * FROM user WHERE username = ?";
        return jdbcTemplate.queryForObject(
            sql,
            new BeanPropertyRowMapper<>(User.class),
            username
        );
    }
}
5.7 启动日志输出
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v3.0.0)

2024-10-27 10:00:00.123  INFO  --- Starting ShopApplication
2024-10-27 10:00:00.456  INFO  --- The following profiles are active: dev

>>> 创建数据源...
>>> JDBC URL: jdbc:mysql://localhost:3306/shop_dev
>>> 最大连接数: 10
>>> 数据源创建完成!

>>> 创建JdbcTemplate...

>>> 创建Redis连接工厂...
>>> Redis Host: localhost:6379

>>> 创建RedisTemplate...
>>> RedisTemplate创建完成!

>>> 创建密码加密器(BCrypt)

>>> 配置Spring Security过滤器链...
>>> 白名单URL: [/api/auth/login, /api/auth/register, /api/products/**]

>>> 创建邮件发送器...
>>> SMTP服务器: smtp.gmail.com
>>> 邮件发送器创建完成!

2024-10-27 10:00:02.789  INFO  --- Tomcat started on port(s): 8080
2024-10-27 10:00:02.890  INFO  --- Started ShopApplication in 2.767 seconds

[应用启动完成,等待请求...]

六、配置类高级用法

6.1 条件装配(@Conditional)

/**
 * 根据条件决定是否创建Bean
 */
@Configuration
public class ConditionalConfig {
    
    /**
     * 只在开发环境创建
     */
    @Bean
    @Profile("dev")
    public MockDataService mockDataService() {
        return new MockDataService();
    }
    
    /**
     * 只在生产环境创建
     */
    @Bean
    @Profile("prod")
    public RealDataService realDataService() {
        return new RealDataService();
    }
    
    /**
     * 只在类路径存在Redis时创建
     */
    @Bean
    @ConditionalOnClass(RedisTemplate.class)
    public CacheService redisCacheService() {
        return new RedisCacheService();
    }
    
    /**
     * 只在配置了某个属性时创建
     */
    @Bean
    @ConditionalOnProperty(name = "feature.email.enabled", havingValue = "true")
    public EmailService emailService() {
        return new EmailService();
    }
    
    /**
     * 只在没有其他EmailService时创建(默认实现)
     */
    @Bean
    @ConditionalOnMissingBean(EmailService.class)
    public EmailService defaultEmailService() {
        return new MockEmailService();
    }
}

6.2 导入其他配置(@Import)

/**
 * 主配置类
 */
@Configuration
@Import({
    DatabaseConfig.class,
    RedisConfig.class,
    SecurityConfig.class
})
public class AppConfig {
    // 主配置
}

// 或者使用@EnableXxx的方式
@Configuration
@EnableCaching        // 启用缓存(会导入CachingConfiguration)
@EnableAsync          // 启用异步(会导入AsyncConfiguration)
@EnableScheduling     // 启用定时任务(会导入SchedulingConfiguration)
public class AppConfig {
}

6.3 Bean的作用域

@Configuration
public class ScopeConfig {
    
    /**
     * 单例(默认)
     * 整个应用只创建一次
     */
    @Bean
    @Scope("singleton")  // 可省略,默认就是singleton
    public UserService userService() {
        return new UserService();
    }
    
    /**
     * 原型
     * 每次获取都创建新对象
     */
    @Bean
    @Scope("prototype")
    public Order order() {
        return new Order();
    }
    
    /**
     * 请求作用域
     * 每个HTTP请求创建一个
     */
    @Bean
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, 
           proxyMode = ScopedProxyMode.TARGET_CLASS)
    public RequestInfo requestInfo() {
        return new RequestInfo();
    }
    
    /**
     * Session作用域
     * 每个Session创建一个
     */
    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, 
           proxyMode = ScopedProxyMode.TARGET_CLASS)
    public UserCart userCart() {
        return new UserCart();
    }
}

作用域对比

┌─────────────────────────────────────────────────────┐
│ singleton(单例)                                     │
│ ────────────────────────────────────────────────    │
│ 应用启动  → 创建1个对象                              │
│ 请求1     → 使用同一个对象                           │
│ 请求2     → 使用同一个对象                           │
│ 请求3     → 使用同一个对象                           │
│ 应用关闭  → 销毁这个对象                             │
│                                                       │
│ 示例:DataSource, UserService, ProductService       │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ prototype(原型)                                     │
│ ────────────────────────────────────────────────    │
│ 每次注入  → 创建新对象                               │
│ @Autowired Order order1; → 对象A                    │
│ @Autowired Order order2; → 对象B(新创建)          │
│                                                       │
│ 示例:订单对象、购物车项                             │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ request(请求作用域)                                 │
│ ────────────────────────────────────────────────    │
│ 请求1开始 → 创建对象A → 请求1结束 → 销毁对象A        │
│ 请求2开始 → 创建对象B → 请求2结束 → 销毁对象B        │
│                                                       │
│ 示例:RequestInfo(请求信息)                        │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ session(会话作用域)                                 │
│ ────────────────────────────────────────────────    │
│ 用户A登录  → 创建购物车A → 用户A登出 → 销毁购物车A   │
│ 用户B登录  → 创建购物车B → 用户B登出 → 销毁购物车B   │
│                                                       │
│ 示例:购物车、用户会话信息                           │
└─────────────────────────────────────────────────────┘

6.4 Bean的生命周期回调

@Configuration
public class LifecycleConfig {
    
    /**
     * 方式1:使用 @Bean 的属性
     */
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public DataSource dataSource() {
        return new HikariDataSource();
    }
    
    /**
     * 方式2:实现接口
     */
    @Bean
    public MyService myService() {
        return new MyService();
    }
}

public class MyService implements InitializingBean, DisposableBean {
    
    /**
     * 初始化回调(Bean创建后)
     */
    @Override
    public void afterPropertiesSet() {
        System.out.println(">>> MyService 初始化完成");
        // 初始化逻辑...
    }
    
    /**
     * 销毁回调(应用关闭前)
     */
    @Override
    public void destroy() {
        System.out.println(">>> MyService 销毁");
        // 清理资源...
    }
}

/**
 * 方式3:使用注解(推荐)
 */
@Component
public class ResourceManager {
    
    private ExecutorService executorService;
    
    /**
     * 初始化方法
     */
    @PostConstruct
    public void init() {
        System.out.println(">>> ResourceManager 初始化");
        executorService = Executors.newFixedThreadPool(10);
    }
    
    /**
     * 销毁方法
     */
    @PreDestroy
    public void cleanup() {
        System.out.println(">>> ResourceManager 清理资源");
        if (executorService != null) {
            executorService.shutdown();
        }
    }
}

生命周期完整流程

Bean的完整生命周期:
═══════════════════════════════════════════════════════

1. 实例化
   └─ Spring调用构造函数创建对象

2. 属性赋值
   └─ @Autowired、@Value 注入依赖

3. Aware接口回调
   ├─ BeanNameAware.setBeanName()
   ├─ BeanFactoryAware.setBeanFactory()
   └─ ApplicationContextAware.setApplicationContext()

4. BeanPostProcessor前置处理
   └─ postProcessBeforeInitialization()

5. 初始化
   ├─ @PostConstruct 方法执行
   ├─ InitializingBean.afterPropertiesSet()
   └─ @Bean(initMethod) 指定的方法

6. BeanPostProcessor后置处理
   └─ postProcessAfterInitialization()

7. Bean可用
   └─ 正常使用

8. 销毁
   ├─ @PreDestroy 方法执行
   ├─ DisposableBean.destroy()
   └─ @Bean(destroyMethod) 指定的方法

七、最佳实践

7.1 配置类的组织结构

✅ 好的组织方式:
src/main/java/com/example/config/
├── DatabaseConfig.java         # 数据库配置
├── RedisConfig.java           # Redis配置
├── SecurityConfig.java        # 安全配置
├── WebMvcConfig.java          # Web MVC配置
├── MessageQueueConfig.java    # 消息队列配置
└── properties/                # 配置属性类
    ├── DatabaseProperties.java
    ├── RedisProperties.java
    └── SecurityProperties.java

优势:
├─ 职责明确
├─ 易于查找
├─ 便于维护
└─ 团队协作友好

❌ 不好的组织方式:
src/main/java/com/example/config/
└── ApplicationConfig.java     # 所有配置混在一起

问题:
├─ 文件过大(1000+行)
├─ 难以定位
├─ 容易冲突
└─ 难以复用

7.2 配置的优先级

配置来源的优先级(从高到低):
═══════════════════════════════════════════════════════

1. 命令行参数
   java -jar app.jar --server.port=8081

2. Java系统属性
   -Dserver.port=8081

3. 操作系统环境变量
   export SERVER_PORT=8081

4. application-{profile}.yml
   application-prod.yml

5. application.yml
   默认配置

6. @PropertySource 指定的文件

7. 默认值(代码中设置的)
   @Value("${server.port:8080}")  # 默认8080

实际应用

# application.yml(默认配置)
server:
  port: 8080

# application-prod.yml(生产环境覆盖)
server:
  port: 80

# 环境变量(最高优先级)
export SERVER_PORT=9000

# 最终结果:
如果设置了环境变量 → 使用9000
否则,如果是prod环境 → 使用80
否则 → 使用8080

7.3 敏感信息处理

# ❌ 错误:敏感信息写在配置文件
spring:
  datasource:
    password: MyPassword123!  # 提交到Git!危险!

# ✅ 正确:使用环境变量
spring:
  datasource:
    password: ${DB_PASSWORD}  # 从环境变量读取

# ✅ 正确:使用Spring Cloud Config
# 从配置中心读取,配置中心会加密存储

生产环境部署

# 方式1:环境变量
export DB_PASSWORD=SecurePassword123
export JWT_SECRET=MyJwtSecret456
java -jar app.jar

# 方式2:启动参数
java -jar app.jar \
  --spring.datasource.password=SecurePassword123 \
  --security.token.secret=MyJwtSecret456

# 方式3:配置文件(不提交到Git)
# application-prod.yml(.gitignore忽略)
spring:
  datasource:
    password: SecurePassword123

# 方式4:使用配置中心(推荐)
# Nacos、Apollo、Spring Cloud Config

7.4 多数据源配置

/**
 * 多数据源配置
 * 
 * 场景:
 * - 主库(写)
 * - 从库(读)
 * - 第三方数据库
 */
@Configuration
public class MultiDataSourceConfig {
    
    /**
     * 主数据源(默认)
     */
    @Bean
    @Primary  // 标记为主数据源
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    /**
     * 从数据源(只读)
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    /**
     * 主数据源的JdbcTemplate
     */
    @Bean
    @Primary
    public JdbcTemplate primaryJdbcTemplate(
            @Qualifier("primaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    /**
     * 从数据源的JdbcTemplate
     */
    @Bean
    public JdbcTemplate secondaryJdbcTemplate(
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

// 配置文件
spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://master-db:3306/shop
      username: root
      password: ${MASTER_DB_PASSWORD}
    secondary:
      jdbc-url: jdbc:mysql://slave-db:3306/shop
      username: readonly
      password: ${SLAVE_DB_PASSWORD}

// 使用
@Service
public class UserService {
    
    @Autowired
    @Qualifier("primaryJdbcTemplate")
    private JdbcTemplate writeDb;  // 写操作
    
    @Autowired
    @Qualifier("secondaryJdbcTemplate")
    private JdbcTemplate readDb;   // 读操作
    
    public void createUser(User user) {
        writeDb.update("INSERT INTO user ...", ...);
    }
    
    public User getUser(Long id) {
        return readDb.queryForObject("SELECT * FROM user ...", ...);
    }
}

八、常见误区

8.1 误区1:配置类会被多次执行

// ❌ 错误理解
@Configuration
public class MyConfig {
    
    @Bean
    public DataSource dataSource() {
        // 我以为每次请求都会执行这个方法
        System.out.println("创建数据源");
        return new HikariDataSource();
    }
}

// ✅ 正确理解
/**
 * @Bean 方法只在启动时执行一次
 * 之后所有地方共享同一个DataSource对象
 */
启动时:
├─ dataSource()执行 → 创建DataSource对象A
└─ 存入容器

请求1:使用对象A
请求2:使用对象A
请求3:使用对象A
...
全都是同一个对象A

8.2 误区2:@Bean方法可以随便调用

// ❌ 错误做法
@Configuration
public class BadConfig {
    
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 直接调用@Bean方法
        DataSource ds = dataSource();  // 危险!
        return new JdbcTemplate(ds);
    }
}

// ✅ 正确做法
@Configuration
public class GoodConfig {
    
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        // 通过参数注入
        return new JdbcTemplate(dataSource);
    }
}

原因:
├─ 直接调用:每次都创建新对象
├─ 参数注入:Spring确保使用同一个对象
└─ Spring会拦截@Bean方法调用,确保单例

8.3 误区3:配置类必须有无参构造函数

// ❌ 错误:以为配置类不能有依赖
@Configuration
public class BadConfig {
    // 无法注入依赖?
}

// ✅ 正确:配置类可以注入依赖
@Configuration
@RequiredArgsConstructor  // Lombok生成构造函数
public class GoodConfig {
    
    private final SecurityProperties properties;  // 可以注入
    private final TokenService tokenService;      // 可以注入
    
    @Bean
    public SecurityFilterChain filterChain() {
        // 使用注入的依赖
        return http
            .addFilter(new TokenFilter(tokenService))
            .build();
    }
}

8.4 误区4:所有配置都放在yml文件

// ❌ 不要什么都放yml
spring:
  datasource:
    url: ...
  security:
    complex-object:  # 复杂对象不适合yml
      filters:
        - name: filter1
          order: 1
          config:
            key1: value1
            key2: value2
      handlers:
        - ...

// ✅ 简单配置用yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db
    username: root
    password: ${DB_PASSWORD}

// ✅ 复杂配置用Java代码
@Configuration
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain() {
        // 复杂的对象组装
        return http
            .addFilter(customFilter1)
            .addFilter(customFilter2)
            .exceptionHandling(...)
            .build();
    }
}

原则:
├─ 简单数据 → yml配置
├─ 复杂对象 → Java配置
└─ 业务逻辑 → Java配置

8.5 误区5:忘记不同环境的配置

// ❌ 只有一个配置文件
application.yml:
  datasource:
    url: jdbc:mysql://localhost:3306/db  # 开发环境地址

部署到生产:崩溃!连接不到localhost

// ✅ 多环境配置
application.yml:  # 公共配置
  application:
    name: myapp

application-dev.yml:  # 开发环境
  datasource:
    url: jdbc:mysql://localhost:3306/dev_db

application-prod.yml:  # 生产环境
  datasource:
    url: jdbc:mysql://prod-db.example.com:3306/prod_db
    password: ${DB_PASSWORD}  # 环境变量

启动时指定环境:
java -jar app.jar --spring.profiles.active=prod

九、总结

核心要点回顾

1. 配置类 ≠ 配置文件
   ├─ 配置类是Java代码
   ├─ 在启动时执行
   └─ 用来创建和组装Bean

2. 执行时机
   ├─ 启动时执行一次
   ├─ @Bean方法被调用
   ├─ 创建的对象放入容器
   └─ 运行时不再执行

3. 核心注解
   ├─ @Configuration:标记配置类
   ├─ @Bean:创建Bean对象
   ├─ @Autowired:注入依赖
   ├─ @Value:读取单个配置
   └─ @ConfigurationProperties:批量读取配置

4. 最佳实践
   ├─ 按功能模块组织配置类
   ├─ 敏感信息用环境变量
   ├─ 多环境用profile
   └─ 简单配置用yml,复杂配置用Java

形象记忆口诀

配置类,搭舞台 🏗️
启动时,只一回 ⏰
创建Bean,存起来 📦
业务来,直接用 ✅
不重建,效率高 🚀
环境切,改参数 🔄
代码稳,配置活 💡

快速检查清单

✅ 每个功能模块有独立配置类
✅ @Configuration 注解没遗漏
✅ @Bean 方法返回类型正确
✅ 敏感信息使用环境变量
✅ 多环境配置已准备
✅ Bean的作用域选择正确
✅ 生命周期回调已实现
✅ 条件装配合理使用
✅ 配置类有清晰的注释
✅ 测试配置已准备

最后总结

配置类就像是建房子的施工队,在应用启动时(开工),按照图纸(代码)把房子(Spring容器)建好。建好之后,住户(业务代码)就可以直接入住使用了,不需要每次都重建房子。

配置类的本质是:在启动时执行的代码,用来创建和组装应用所需的各种对象,这些对象会被Spring管理并在运行时重复使用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐