乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Configuration 详解

本章定位:深入理解 Spring 的 Java Config 配置模式。@Configuration 是 Spring 从 XML 配置向类型安全、IDE 友好的编程式配置演进的核心注解,是现代 Spring 应用的配置基石。


定义与作用

@Configuration 是 Spring 提供的配置类注解,用于标记一个类为 Spring 的配置类。在该类中定义的 @Bean 方法将被容器特殊处理,确保方法返回的 Bean 遵循单例语义。

解决的痛点:早期 Spring 使用 XML 配置,存在类型不安全、重构困难、冗长繁琐等问题:

<!-- 痛点:XML 配置冗长,无编译期检查 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/student_db"/>
    <property name="username" value="root"/>
    <property name="password" value="secret"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

@Configuration 让配置类型安全、可重构、可编程,彻底告别 XML 的字符串引用:

@Configuration
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db");
        config.setUsername("root");
        config.setPassword("secret");
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

与 @Component 的关键差异

@Configuration 虽然标注了 @Component,但容器对其有特殊处理——方法拦截(Method Interception):


适用位置与常用属性

适用位置

@Configuration 只能标注在类级别,通常作为应用的主配置类或模块化配置类。

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

常用属性

属性类型默认值说明
valueString""指定配置类的 Bean 名称
proxyBeanMethodsbooleantrue是否通过 CGLIB 代理拦截 @Bean 方法调用,确保单例语义

proxyBeanMethods 详解

从 Spring 5.2 开始,@Configuration 增加了 proxyBeanMethods 属性:

// 默认:启用方法代理(Full 模式)
@Configuration
public class AppConfig {
    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 这里调用 dataSource() 会被代理拦截,返回容器中的单例
        return new JdbcTemplate(dataSource());
    }
}
// Lite 模式:不启用方法代理,性能略优
@Configuration(proxyBeanMethods = false)
public class LiteConfig {
    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 这里调用 dataSource() 会执行方法体,创建新实例!
        return new JdbcTemplate(dataSource());
    }
}

Lite 模式适用于配置类内部不存在 @Bean 方法互相调用的场景,可减少 CGLIB 代理开销。


核心原理:CGLIB 方法拦截

@Configuration 类在容器启动时会被 CGLIB 生成子类,@Bean 方法被重写为"先查容器缓存,再决定是否执行方法体":

源码层面的实现:

// CGLIB 生成的代理类(伪代码)
public class AppConfig$$EnhancerBySpringCGLIB extends AppConfig {

    @Override
    public DataSource dataSource() {
        // 先检查容器中是否已有该 Bean
        if (this.beanFactory.containsBean("dataSource")) {
            return this.beanFactory.getBean("dataSource", DataSource.class);
        }
        // 没有则调用父类方法创建
        DataSource ds = super.dataSource();
        // 注册到容器
        this.beanFactory.registerSingleton("dataSource", ds);
        return ds;
    }
}

完整示例:飞翔科技公司的多环境配置

场景简述

飞翔科技的学生管理系统需要支持开发环境(dev)和生产环境(prod)。架构师白歌要求使用 @Configuration 将数据源、模板引擎等基础设施的配置集中管理,并支持按环境切换。

操作前:XML 配置的维护噩梦

<!-- 操作前:application-context.xml —— 冗长且类型不安全 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="jdbcUrl" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
        <property name="maximumPoolSize" value="${db.pool.size}"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

痛点:

  1. 属性名拼写错误(如 jdbcUrl 写成 jdbcURL)在编译期无法发现
  2. IDE 重构类名时,XML 中的全限定类名不会自动更新
  3. 条件配置(如 dev 用 H2,prod 用 MySQL)需要多套 XML 文件
  4. 无法使用 Java 的编程能力(如 if/else、循环)动态构建配置

操作后:使用 @Configuration 的 Java Config

// 操作后:AppConfig.java —— 主配置类
@Configuration
@ComponentScan("com.feixiang.student")
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource(
            @Value("${db.url}") String url,
            @Value("${db.username}") String username,
            @Value("${db.password}") String password,
            @Value("${db.pool.size:10}") int poolSize) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        config.setMaximumPoolSize(poolSize);
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
// 操作后:DevDataSourceConfig.java —— 开发环境专属配置
@Configuration
@Profile("dev")
public class DevDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // 开发环境使用嵌入式 H2 数据库,无需外部 MySQL
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:data.sql")
            .build();
    }
}
// 操作后:ProdDataSourceConfig.java —— 生产环境专属配置
@Configuration
@Profile("prod")
public class ProdDataSourceConfig {

    @Bean
    public DataSource dataSource(
            @Value("${db.url}") String url,
            @Value("${db.username}") String username,
            @Value("${db.password}") String password) {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(url);
        config.setUsername(username);
        config.setPassword(password);
        config.setMaximumPoolSize(50);
        config.setConnectionTimeout(30000);
        return new HikariDataSource(config);
    }
}
// 操作后:启动类
public class Main {
    public static void main(String[] args) {
        // 开发环境启动
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("dev");
        ctx.register(AppConfig.class);
        ctx.refresh();

        DataSource ds = ctx.getBean(DataSource.class);
        System.out.println("当前数据源:" + ds.getClass().getSimpleName());
        // 输出:当前数据源:EmbeddedDatabaseFactory$EmbeddedDataSourceProxy
    }
}

运行结果及分析

开发环境(dev)启动:

[main] INFO  o.s.c.a.AnnotationConfigApplicationContext - 
    Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] INFO  o.s.j.d.e.EmbeddedDatabaseFactory - 
    Starting embedded database 'testdb'
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'dataSource'
[main] INFO  c.f.s.config.Main - 当前数据源:EmbeddedDatabaseFactory$EmbeddedDataSourceProxy

生产环境(prod)启动:

[main] INFO  o.s.c.a.AnnotationConfigApplicationContext - 
    Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Creating shared instance of singleton bean 'dataSource'
[main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
[main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed
[main] INFO  c.f.s.config.Main - 当前数据源:HikariDataSource

改进点总结:

维度操作前(XML)操作后(@Configuration)
类型安全字符串配置,编译期不检查Java 代码,编译期强类型检查
IDE 重构类名变更后 XML 需手动更新IDE 自动重构所有引用
条件逻辑需多套 XML 文件@Profile + if/else 编程式控制
可读性XML 冗长,嵌套层级深Java 代码,结构清晰
调试难以断点可在 @Bean 方法中打断点

@Configuration 的模块化组织

大型项目中,配置类应按职责拆分:

// 数据源配置
@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() { ... }
}

// 事务配置
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource ds) { ... }
}

// Web 配置
@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean loggingFilter() { ... }
}

// 主配置类:聚合所有模块
@Configuration
@Import({DataSourceConfig.class, TransactionConfig.class, WebConfig.class})
@ComponentScan("com.feixiang.student")
public class AppConfig {
}

易错场景与面试考点

反例一:@Configuration 类中直接调用 @Bean 方法导致多实例

小崔在 Lite 模式下调用 @Bean 方法,发现每个依赖都创建了新的 DataSource:

// ❌ 错误:proxyBeanMethods = false 时,@Bean 方法互相调用会创建多实例
@Configuration(proxyBeanMethods = false)
public class LiteConfig {

    @Bean
    public DataSource dataSource() {
        System.out.println("创建 DataSource");
        return new HikariDataSource();
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 这里调用 dataSource() 会执行方法体,创建新的 DataSource!
        return new JdbcTemplate(dataSource());
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // 又创建了一个新的 DataSource!
        return new DataSourceTransactionManager(dataSource());
    }
}

运行输出:

创建 DataSource  ← jdbcTemplate 调用
创建 DataSource  ← transactionManager 调用

问题:产生了 3 个 DataSource 实例,连接池被重复初始化,资源浪费且事务管理器与 JdbcTemplate 使用不同的连接池。

纠正:使用默认的 Full 模式(proxyBeanMethods = true),或通过方法参数注入:

// ✅ 方案一:使用 Full 模式(默认)
@Configuration
public class FullConfig {
    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 被代理拦截,返回容器中的单例 DataSource
        return new JdbcTemplate(dataSource());
    }
}

// ✅ 方案二:通过方法参数注入(推荐,更明确)
@Configuration(proxyBeanMethods = false)
public class LiteConfig {
    @Bean
    public DataSource dataSource() { ... }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        // 由容器注入唯一的 DataSource
        return new JdbcTemplate(dataSource);
    }
}

反例二:在 @Configuration 类中使用 @Autowired 字段注入

// ❌ 错误:配置类中使用字段注入,破坏不可变性
@Configuration
public class AppConfig {

    @Autowired  // 字段注入,不推荐
    private Environment env;

    @Bean
    public DataSource dataSource() {
        String url = env.getProperty("db.url");
        // ...
    }
}

纠正:配置类也应使用构造器注入:

// ✅ 正确:配置类使用构造器注入
@Configuration
public class AppConfig {

    private final Environment env;

    public AppConfig(Environment env) {
        this.env = env;
    }

    @Bean
    public DataSource dataSource() {
        String url = env.getProperty("db.url");
        // ...
    }
}

反例三:@Configuration 类被误用为 @Component

小崔在 @Configuration 类中写了业务逻辑:

// ❌ 错误:@Configuration 类中混入业务逻辑
@Configuration
public class AppConfig {

    @Bean
    public DataSource dataSource() { ... }

    // 业务逻辑不应该出现在配置类中!
    public void enrollStudent(Student student) {
        // ...
    }
}

纠正:配置类只负责 Bean 定义和基础设施配置,业务逻辑放在 @Service 中:

// ✅ 正确:配置类只负责配置
@Configuration
public class AppConfig {
    @Bean
    public DataSource dataSource() { ... }
}

// ✅ 正确:业务逻辑放在 @Service
@Service
public class StudentService {
    public void enrollStudent(Student student) { ... }
}

面试高频题

Q1:@Configuration 和 @Component 有什么区别?

@Configuration 标注了 @Component,因此也会被组件扫描注册为 Bean。但 @Configuration 有特殊处理:容器通过 CGLIB 为其生成代理子类,拦截 @Bean 方法调用,确保方法内部互相调用时返回的是容器中的单例 Bean,而非多次执行方法体创建新实例。这是 @Component 不具备的能力。

Q2:proxyBeanMethods = false 有什么作用?

关闭 @Bean 方法代理(Lite 模式)。适用于配置类内部不存在 @Bean 方法互相调用的场景,可减少 CGLIB 代理开销,提升启动性能。但如果配置类中存在 @Bean 方法互相调用(如 jdbcTemplate() 中调用 dataSource()),必须使用 proxyBeanMethods = true(Full 模式),否则会产生多实例。

Q3:@Bean 方法为什么能保证单例?

在 Full 模式下,@Configuration 类被 CGLIB 代理。@Bean 方法被重写为:先检查容器中是否已有该名称的 Bean,如果有则直接返回,不再执行方法体。因此即使多次调用,也只会创建一次实例。

Q4:@Configuration 类可以互相导入吗?

可以,通过 @Import 注解实现。被导入的 @Configuration 类中的 @Bean 方法同样会被处理。这是 Spring 模块化配置的核心机制,用于将大型配置拆分为多个独立的配置类。

上一页
IoC 与 DI 核心概念
下一页
@Component 详解