Spring Bean注解终极指南:从入门到精通
以下这段内容是 Spring Framework 官方文档中关于 @Bean 注解的详细说明,属于 Spring 核心功能之一 —— 基于 Java 的配置方式(Java-based Configuration)。我们来一步步、通俗易懂地解释每一部分的核心含义和实际意义。
🌱 一、什么是 @Bean?
✅ 简单理解:
@Bean 是一个写在方法上的注解,它的作用是:
“告诉 Spring 容器:这个方法会创建并返回一个对象,把这个对象放进 Spring 容器里管理起来,以后别人要用的时候就从容器拿。”
它相当于 XML 配置文件中的 <bean> 标签。
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
等价于:
@Bean
public TransferService transferService() {return new TransferServiceImpl();
}
🔧 二、基本用法:声明一个 Bean
示例代码:
@Configuration
public class AppConfig {@Beanpublic TransferServiceImpl transferService() {return new TransferServiceImpl();}
}
@Configuration:表示这是一个“配置类”,Spring 会扫描它里面的@Bean方法。@Bean:标记transferService()方法,表示该方法返回的对象要注册为 Spring 的 bean。- 默认情况下,bean 的名字就是方法名 →
"transferService" - 类型是方法的返回值类型 →
TransferServiceImpl
✅ 最终效果:
Spring 容器中就有了一个名为 transferService 的 bean,类型为 TransferServiceImpl。
⚠️ 三、返回接口 vs 返回实现类
你可以这样写:
@Bean
public TransferService transferService() {return new TransferServiceImpl();
}
这里返回的是接口 TransferService,而不是具体实现类。
❓ 有什么区别?
| 写法 | 含义 |
|---|---|
TransferServiceImpl transferService() | Spring 能知道完整类型(包括实现细节) |
TransferService transferService() | Spring 只知道它是 TransferService 接口 |
💡 实际影响:
- 如果其他地方使用
@Autowired TransferServiceImpl(按实现类注入),而你的@Bean方法返回的是接口类型,可能会出错或匹配不到,因为 Spring 在早期不知道它是TransferServiceImpl。 - 所以建议:如果你的 bean 实现了多个接口,或者可能被实现类引用,最好返回最具体的类型。
但如果你的设计原则是“面向接口编程”,所有依赖都通过接口注入,那返回接口也没问题。
🔗 四、Bean 的依赖注入(构造依赖)
@Bean 方法可以有参数,Spring 会自动帮你把需要的依赖“塞进来”。
示例:
@Bean
public TransferService transferService(AccountRepository accountRepository) {return new TransferServiceImpl(accountRepository);
}
✅ 发生了什么?
accountRepository是一个参数。- Spring 会在容器中找一个类型为
AccountRepository的 bean。 - 找到了就传进去;没找到就报错。
👉 这种机制类似于 构造函数注入,非常强大且类型安全。
🔄 五、生命周期回调(初始化 & 销毁)
Spring 提供了很多方式让你控制 bean 的初始化和销毁逻辑。
方法 1:使用 @PostConstruct 和 @PreDestroy(推荐)
public class MyBean {@PostConstructpublic void init() {System.out.println("初始化工作");}@PreDestroypublic void cleanup() {System.out.println("清理资源");}
}
✅ JSR-250 标准注解,通用性强。
方法 2:实现 Spring 接口
public class MyBean implements InitializingBean, DisposableBean {@Overridepublic void afterPropertiesSet() throws Exception {// 初始化}@Overridepublic void destroy() throws Exception {// 销毁}
}
不推荐直接使用,侵入性强。
方法 3:自定义 init/destroy 方法 + @Bean 属性
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyBean myBean() {return new MyBean();
}
public class MyBean {public void init() { /* 初始化 */ }public void cleanup() { /* 清理 */ }
}
✅ 推荐!灵活又不耦合 Spring API。
特别注意:destroyMethod = "" 的用途
默认情况下,如果 bean 有 close() 或 shutdown() 方法,Spring 会自动调用它作为销毁方法。
但这对某些资源(比如 JNDI 获取的 DataSource)是有害的,因为它们的生命周期由外部容器(如 Tomcat)管理。
所以要禁用自动销毁:
@Bean(destroyMethod = "")
public DataSource dataSource() {return jndiTemplate.lookup("java:comp/env/jdbc/myDS");
}
" "表示“不要自动推断销毁方法”。
📦 六、Bean 的作用域(Scope)
默认所有 bean 都是 单例(singleton):整个应用只有一个实例。
但可以用 @Scope 改变作用域。
常见作用域:
| Scope | 说明 |
|---|---|
singleton | 单例(默认),每个容器一个实例 |
prototype | 每次获取都新建一个实例 |
request | 每个 HTTP 请求一个实例(Web 环境) |
session | 每个用户会话一个实例(Web 环境) |
示例:原型模式
@Bean
@Scope("prototype")
public Encryptor encryptor() {return new Encryptor();
}
每次 context.getBean("encryptor") 都会创建新对象。
🎭 七、Scoped Proxy(作用域代理)
有时候你想在一个单例 bean 中使用一个 session-scoped 的 bean(比如保存用户偏好设置),直接注入会出问题,因为单例只会初始化一次。
解决方案:用代理!
@Bean
@SessionScope
public UserPreferences userPreferences() {return new UserPreferences();
}@Bean
public Service userService() {Service service = new SimpleUserService();service.setUserPreferences(userPreferences()); // 注意:这里拿到的是代理对象!return service;
}
👉 实际上 userPreferences() 返回的是一个“代理”,真正的对象会在运行时根据当前 session 动态获取。
这就像懒加载 + 多实例隔离。
🏷️ 八、自定义 Bean 名称 & 别名
自定义名称:
@Bean("myThing")
public Thing thing() {return new Thing();
}
现在 bean 名叫 "myThing",不再是方法名。
设置多个别名:
@Bean({"dataSource", "db", "main-ds"})
public DataSource dataSource() {return createDataSource();
}
这三个名字都可以用来获取同一个 bean!
ℹ️ 九、添加描述信息(用于监控)
@Bean
@Description("主数据源,连接生产数据库")
public DataSource dataSource() {return ...;
}
这个描述可以在 JMX 监控工具中看到,帮助运维人员理解 bean 的用途。
✅ 总结:一张表掌握 @Bean
| 功能 | 如何实现 |
|---|---|
| 声明 bean | @Bean 方法返回对象 |
| 自定义名字 | @Bean("name") |
| 多个别名 | @Bean({"a","b"}) |
| 注入依赖 | 方法参数自动注入 |
| 初始化方法 | @Bean(initMethod="xxx") 或 @PostConstruct |
| 销毁方法 | @Bean(destroyMethod="xxx") 或 @PreDestroy |
| 关闭自动销毁 | @Bean(destroyMethod="")(如 DataSource) |
| 修改作用域 | @Scope("prototype") |
| 使用作用域代理 | @Scope(proxyMode=TARGET_CLASS) 或配合 @RequestScope/@SessionScope |
| 添加描述 | @Description("xxx") |
🧠 最佳实践建议
- 优先使用方法参数注入依赖,而不是手动
new。 - 初始化逻辑尽量用
initMethod或@PostConstruct,避免在@Bean方法里做太多事。 - 对外暴露的 bean 尽量返回接口类型,内部使用的可返回具体类。
- JNDI 数据源一定要加
destroyMethod="",防止服务器关闭时报错。 - 合理使用作用域和代理,特别是在 Web 应用中处理 request/session 级别的数据。
如果你想更进一步,可以把这些 @Bean 方法拆分到不同的 @Configuration 类中,结合 @Import 或组件扫描进行模块化管理。
如有具体场景(比如:“我想让某个 service 每次都新建” 或 “怎么注入配置文件里的值?”),欢迎继续提问!
