使用 @Bean 注解
@Bean Annotation
@Bean 是一个方法级别的注解,是 XML 中的 <bean/> 元素的直接对应物。该注解支持 <bean/> 提供的一些属性,例如:
你可以在被@Configuration注解或@Component注解的类中使用@Bean注解。
声明一个Bean
要声明一个bean,你可以使用@Bean注解来标注一个方法。通过这个方法,你可以在方法返回值所指定的类型ApplicationContext中注册bean定义。默认情况下,bean的名称与方法名称相同。以下示例展示了一个@Bean方法的声明:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
前面的配置与以下Spring XML完全等效:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都使得一个名为 transferService 的 Bean 在 ApplicationContext 中可用,并且该 Bean 与类型为 TransferServiceImpl 的对象实例关联,如下图所示:
transferService -> com.acme.TransferServiceImpl
您还可以使用默认方法来定义bean。这样可以通过在默认方法上实现与bean定义相关的接口来组合bean配置。
- Java
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
你也可以像下面的例子所示,使用接口(或基类)的返回类型来声明你的@Bean方法:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
然而,这限制了类型预测的可见性,仅限于指定的接口类型(TransferService)。只有当受影响的单例bean被实例化后,容器才能知道完整的类型(TransferServiceImpl)。非懒加载的单例bean会按照它们的声明顺序进行实例化,因此,根据其他组件尝试通过未声明的类型进行匹配的时间不同(例如@Autowired TransferServiceImpl,只有当transferService bean被实例化后才会被解析),你可能会看到不同的类型匹配结果。
如果你始终通过声明的服务接口来引用你的类型,那么你的@Bean返回类型就可以安全地遵循这一设计决策。然而,对于实现多个接口的组件,或者可能通过其实现类型被引用的组件来说,声明尽可能具体的返回类型会更为安全(至少要具体到引用该Bean的注入点所要求的程度)。
Bean 依赖关系
一个带有@Bean注解的方法可以有任意数量的参数,这些参数用于描述构建该bean所需的依赖关系。例如,如果我们的TransferService需要一个AccountRepository,我们可以通过方法参数来实现这一依赖关系,如下例所示:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解决机制与基于构造函数的依赖注入几乎完全相同。有关更多详细信息,请参阅相关章节。
接收生命周期回调
任何使用@Bean注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct和@PreDestroy注解。有关更多详细信息,请参阅JSR-250注解。
常规的Spring 生命周期回调也得到了完全支持。如果一个bean实现了InitializingBean、DisposableBean或Lifecycle,容器将会调用它们各自的方法。
标准的一组*Aware接口(如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware等也得到了完全支持。
@Bean 注解支持指定任意的初始化和销毁回调方法,这与 Spring XML 中 bean 元素上的 init-method 和 destroy-method 属性非常相似,如下例所示:
- Java
- Kotlin
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
@Configuration
class AppConfig {
@Bean(initMethod = "init")
fun beanOne() = BeanOne()
@Bean(destroyMethod = "cleanup")
fun beanTwo() = BeanTwo()
}
默认情况下,使用Java配置定义的Bean如果具有public的close或shutdown方法,将会自动被注册为销毁回调。如果你拥有这样的方法但不希望在容器关闭时它们被调用,你可以在Bean定义中添加@Bean(destroyMethod = "")来禁用这一默认(推断)模式。
对于通过JNDI获取的资源,你可能希望默认采取这种做法,因为其生命周期是由应用程序外部管理的。特别是对于DataSource来说,这一点尤为重要,因为在Jakarta EE应用服务器上这可能会导致问题。
以下示例展示了如何防止DataSource的自动销毁回调:
- Java
- Kotlin
@Bean(destroyMethod = "")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
return jndiTemplatelookup("MyDS") as DataSource
}
此外,使用@Bean方法时,通常会采用编程式的JNDI查找方式,可以通过Spring的JndiTemplate或JndiLocatorDelegate辅助工具来实现,也可以直接使用JNDI的InitialContext,但不要使用JndiObjectFactoryBean变体(因为那样会强制你将返回类型声明为FactoryBean类型,而非实际的目标类型,从而使得在其他@Bean方法中引用该资源时更加复杂)。
在上述示例中的BeanOne情况下,如以下示例所示,在构造过程中直接调用init()方法也是完全可行的:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
当你直接使用Java进行开发时,你可以随意操作的对象,而不总是需要依赖于容器的生命周期。
指定Bean作用域
Spring 包含了 @Scope 注解,以便你可以指定 Bean 的作用域。
使用 @Scope 注解
你可以指定使用@Bean注解定义的Bean应具有特定的作用域。你可以使用在Bean Scopes部分中指定的任何标准作用域。
默认范围是 singleton,但你可以使用 @Scope 注解来覆盖这个设置,如下例所示:
- Java
- Kotlin
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope 和 scoped-proxy
Spring提供了一种方便的方式来处理有作用域的依赖关系,通过作用域代理实现。在使用XML配置时,创建此类代理的最简单方法是使用<aop:scoped-proxy/>元素。在Java中,通过@Scope注解进行bean配置也可以通过proxyMode属性提供等效的支持。默认值是ScopedProxyMode.DEFAULT,这通常表示除非在component-scan指令级别配置了不同的默认值,否则不应创建任何作用域代理。你可以指定ScopedProxyMode.TARGET_CLASS、ScopedProxyMode.INTERFACES或ScopedProxyMode.NO。
如果你将XML参考文档中的作用域代理示例(见作用域代理)移植到我们使用Java的@Bean中,它将类似于以下内容:
- Java
- Kotlin
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean
setUserPreferences(userPreferences())
}
}
自定义Bean命名
默认情况下,配置类会使用@Bean方法的名称作为生成的bean的名称。然而,正如以下示例所示,可以通过name属性来覆盖这一功能:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean 别名
如命名Bean中所讨论的,有时需要为单个Bean赋予多个名称,这被称为Bean别名。为此,@Bean注解的name属性可以接受一个字符串数组。以下示例展示了如何为一个Bean设置多个别名:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供更详细的文本描述会很有帮助。当beans(可能是通过JMX)被暴露出来用于监控目的时,这一点尤其有用。
要在@Bean上添加描述,可以使用[@Description](https://docs.spring.io/spring-framework/docs/7.0.3/javadoc-api/org/springframework/context/annotation/Description.html)注解,如下例所示:
- Java
- Kotlin
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}