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

    • 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 核心注解速查表

@Bean 详解

定义与作用

@Bean 是 Spring Java Config 配置方式的核心注解,用于在标注了 @Configuration 的类中显式声明一个 Bean 的定义。与组件扫描(@ComponentScan)自动发现类路径下的 @Component、@Service 等注解不同,@Bean 赋予开发者对 Bean 创建过程的完全控制权:你可以决定如何实例化对象、如何设置其属性、如何在创建前后插入自定义逻辑。

在飞翔科技的学生管理系统中,架构师白歌经常遇到这样的场景:某些第三方库提供的类(如数据库连接池、消息队列客户端)无法修改源码,自然也无法在其类上加 @Component。此时,@Bean 就是唯一能将这类对象纳入 Spring 容器管理的途径。

适用位置与常用属性

属性类型说明
name / valueString[]指定 Bean 的名称,默认取方法名
initMethodString指定初始化方法名,等价于 @PostConstruct
destroyMethodString指定销毁方法名,等价于 @PreDestroy,默认值为 (inferred) 会自动探测 close / shutdown 方法

适用位置:只能标注在方法上,且该方法必须定义在 @Configuration 类中(或在 @Component 类中,但行为有差异,见易错场景)。

核心原理

当 Spring 容器加载一个 @Configuration 类时,会通过 CGLIB 生成该类的代理子类。代理子类会拦截所有 @Bean 方法的调用,确保同一个 @Bean 方法在多次被依赖引用时,返回的是容器中的同一个实例(对于 singleton 作用域)。

关键机制:@Configuration 类中的 @Bean 方法之间的互相调用(如 jdbcTemplate() 调用 dataSource())也会被代理拦截,从而保证依赖的 Bean 同样是经过容器管理的单例,而非普通 Java 方法调用产生的新对象。

完整示例

场景简述

飞翔科技的学生管理系统需要连接 MySQL 数据库。后端开发小崔负责配置数据源和 JdbcTemplate。由于 HikariDataSource 和 JdbcTemplate 都是第三方类,无法直接加 @Component,小崔决定用 @Bean 显式装配。

操作前:无 Spring 管理的纯手动创建

// 没有 Spring 容器,每次需要数据源的地方都手动 new
public class StudentDao {
    private javax.sql.DataSource dataSource;

    public StudentDao() {
        // 硬编码配置,无法切换环境
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db");
        config.setUsername("root");
        config.setPassword("secret");
        this.dataSource = new HikariDataSource(config);
    }

    public List<Student> findAll() {
        // 每次都要手动处理连接关闭
        try (Connection conn = dataSource.getConnection()) {
            // ... 查询逻辑
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

痛点分析:

  • 配置与代码耦合,测试环境、生产环境切换困难
  • 连接池无法复用,每个 DAO 都创建一个,资源浪费
  • 没有生命周期管理,应用关闭时连接池无法优雅释放

使用该注解的完整代码

package com.feixiang.student.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean(destroyMethod = "close")
    @Profile("dev")
    public DataSource devDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/student_dev");
        config.setUsername("dev_user");
        config.setPassword("dev_pass");
        config.setMaximumPoolSize(5);
        return new HikariDataSource(config);
    }

    @Bean(destroyMethod = "close")
    @Profile("prod")
    public DataSource prodDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://db.learnto.cn:3306/student_prod");
        config.setUsername("prod_user");
        config.setPassword("prod_pass");
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
package com.feixiang.student.dao;

import com.feixiang.student.entity.Student;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class StudentDao {

    private final JdbcTemplate jdbcTemplate;

    // 构造器注入:Spring 自动将 @Bean 定义的 JdbcTemplate 注入进来
    public StudentDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Student> findAll() {
        String sql = "SELECT id, name, age, major FROM student";
        return jdbcTemplate.query(sql, (rs, rowNum) -> new Student(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getInt("age"),
                rs.getString("major")
        ));
    }
}
package com.feixiang.student;

import com.feixiang.student.config.DataSourceConfig;
import com.feixiang.student.dao.StudentDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class StudentApplication {
    public static void main(String[] args) {
        // 激活 dev 环境
        System.setProperty("spring.profiles.active", "dev");

        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(DataSourceConfig.class);

        StudentDao studentDao = ctx.getBean(StudentDao.class);
        System.out.println("查询到的学生数量:" + studentDao.findAll().size());

        ctx.close(); // 容器关闭时,HikariDataSource 的 close() 方法会被自动调用
    }
}

操作后运行结果及分析

查询到的学生数量:3

变化分析:

  • 配置与代码解耦:数据库连接信息集中在 DataSourceConfig 中,业务代码 StudentDao 不再关心数据源如何创建
  • 单例复用:devDataSource() 和 jdbcTemplate() 返回的实例被缓存于 Spring 容器的单例池中,StudentDao 和其他 DAO 共享同一个连接池和 JdbcTemplate
  • 生命周期托管:@Bean(destroyMethod = "close") 确保容器关闭时调用 HikariDataSource.close(),释放所有数据库连接
  • 环境切换:通过 @Profile 配合 spring.profiles.active,开发环境连本地库,生产环境连线上库,无需修改任何 Java 代码

易错场景与面试考点

易错场景一:在 @Component 类中使用 @Bean

@Component
public class WrongConfig {

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

    @Bean
    public JdbcTemplate jdbcTemplate() {
        // 这里的 dataSource() 是普通方法调用,会创建一个新的 DataSource!
        return new JdbcTemplate(dataSource());
    }
}

后果:jdbcTemplate() 内部调用的 dataSource() 不会被 Spring 代理拦截,导致创建了两个不同的 DataSource 实例:一个被 Spring 容器管理,一个被 JdbcTemplate 私有持有。连接池配置失效,资源泄漏。

正确做法:将 @Component 改为 @Configuration,或确保 @Bean 方法之间的依赖通过方法参数传入(Spring 会自动注入容器中的 Bean):

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource); // 容器自动注入已存在的单例
}

面试考点

Q:@Bean 的方法名和 Bean 名称有什么关系?

默认情况下,Bean 的名称就是方法名。可以通过 @Bean("customName") 或 @Bean(name = "customName") 自定义。如果方法名重载,Spring 会报错,因为 Bean 名称必须唯一。

Q:@Bean 和 @Component 有什么区别?

@Component 及其派生注解(@Service、@Repository 等)用于标记类,由组件扫描自动发现并注册为 Bean,适用于自己编写的类。@Bean 用于标记方法,在配置类中显式定义 Bean 的创建逻辑,适用于第三方库的类或需要复杂初始化逻辑的场景。

Q:@Configuration 和 @Component 中定义 @Bean 的最大区别是什么?

@Configuration 类会被 CGLIB 代理,确保 @Bean 方法之间的内部调用被拦截,返回容器中的单例;而 @Component 类中的 @Bean 方法只是普通方法调用,每次调用都会执行方法体,可能创建新实例。

上一页
Bean的定义与依赖注入
下一页
@Autowired 详解