在 Component 类中定义 bean 元数据
一般,是在 @Configuration
注解的类中通过 @Bean
方法来定义 bean,同样,Spring 的 @Component
类中也可以用来向容器定义 bean 元数据,使用相同的 @Bean
方法就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component public class FactoryMethodComponent {
@Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } public void doWork() { } }
|
将 InjectionPoint 作为 @Bean 方法的参数
从 Spring 4.3 开始,可以声明一个工厂方法,它的参数类型为 org.springframework.beans.factory.InjectionPoint
,用来访问触发当前 bean 创建的请求注入点, 但是这只适用于 bean 实例实际的创建,而不是对已存在实例的注入。所以在原型(prototype)bean 的场景下意义比较大.
如下的例子中,将 userInfo
声明为一个原型 bean,并提供了 InjectionPoint
对象作为 @Bean
方法的参数,在另外两个 bean 的定义中注入 UserInfo 类型的 bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package com.example.springboot.demo.component;
import com.example.springboot.demo.entity.UserInfo; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component;
@Component public class FactoryMethodComponent {
@Bean @Scope("prototype") public UserInfo userInfo(InjectionPoint injectionPoint) { System.out.println("prototype instance for " + injectionPoint.getMember()); return new UserInfo(); }
@Bean public PropertiesFactoryBean overrideProperties(@Autowired ResourceLoader resourceLoader, @Autowired UserInfo userInfo) {
System.out.println("inject userInfo from overrideProperties:" + userInfo.hashCode());
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(resourceLoader.getResource("classpath:override.properties")); propertiesFactoryBean.setFileEncoding("UTF-8"); return propertiesFactoryBean; }
@Bean public PropertiesFactoryBean applicationProperties(@Autowired ResourceLoader resourceLoader, @Autowired UserInfo userInfo) {
System.out.println("inject userInfo from applicationProperties:" + userInfo.hashCode()); PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); propertiesFactoryBean.setLocation(resourceLoader.getResource("classpath:application.properties")); propertiesFactoryBean.setFileEncoding("UTF-8"); return propertiesFactoryBean; }
}
|
当容器启动后,可以在控制台中打印进行验证:
1 2 3 4 5 6 7
| prototype instance for public org.springframework.beans.factory.config.PropertiesFactoryBean com.example.springboot.demo.component.FactoryMethodComponent.overrideProperties(org.springframework.core.io.ResourceLoader,com.example.springboot.demo.entity.UserInfo)
inject userInfo from overrideProperties:542895457
prototype instance for public org.springframework.beans.factory.config.PropertiesFactoryBean com.example.springboot.demo.component.FactoryMethodComponent.applicationProperties(org.springframework.core.io.ResourceLoader,com.example.springboot.demo.entity.UserInfo)
inject userInfo from applicationProperties:2049646260
|
Component 类和 Configuration 类中声明 bean 定义的区别
@Component
类中的 @Bean
方法的处理与 @Configuration
中的处理不同, 区别在于对于 @Component
类, CGLIB 并不会拦截对方法和字段的调用,而在 @Configuration
类中,CGLIB 的代理是通过对 @Bean
方法中的方法和字段调用来创建对协作对象(依赖)的 bean 元数据引用(bean metadata references), 这样的方法并不是通过一般的 Java 语义 进行调用的,而是通过容器来提供声明周期管理和 Spring beans 的代理, 即使是通过程序式的调用某个 @bean
方法来引用该 bean.
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class AppConfig {
@Bean public BeanOne beanOne() { return new BeanOne(beanTwo()); }
@Bean public BeanTwo beanTwo() { return new BeanTwo(); } }
|
在 Component
类中的某个 @Bean
方法中,调用其它方法或字段,只是标准的 Java 方法调用语义,并没有特殊的 CGLIB 的处理和约束.
静态 @bean 方法
@Bean
方法也可以声明为静态的,这样就不需在创建所在 Configuration 或 Component 实例后,才能调用。当定义后置处理器 bean 时非常有意义,如 BeanFactoryPostProcessor
或者是 BeanPostProcessor
, 这是因为这些 bean 需要在容器生命周期的早期就进行初始化,并且要避免对配置类的其它部分进行初始化。
如,Spring Boot 中的的自动配置类 PropertySourcesPlaceholderConfigurer
(PropertySource/bean property externalized BeanFactoryPostProcessor )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class PropertyPlaceholderAutoConfiguration {
@Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); }
}
|
对静态 @Bean
方法的调用,并不会被容器拦截,即使是定义在 @Configuration
类中,这是由于技术的限制:CGLIB 子类只能覆盖非静态方法,所以在某个 @Bean
方法中,对这些静态 @Bean
方法的调用遵循标准的 Java 语义,返回一个新的实例对象.
@Bean 方法的可见性
@Bean
方法的 Java 语言可见性不会对 Spring 容器中生成的 bean 定义产生直接影响。在非 @Configurtation
类中, 可以自由定义方法的可见性 (无论是静态的还是非静态的), 但是在 @Configuration
类中, 一般的 @Bean
方法式需要是可覆盖的 (代理子类), 所以不能被声明为 private
和 final
.
@Bean
方法可以被声明在给定的 component 或者 configuration 类的基类中, 或者是这些类所实现的接口的默认方法上 (Java 8 default method). 这样在组合复杂的配置时, 可以具有更多的灵活性.
参考阅读
Core Technologies (spring.io) - Defining Bean Metadata within Components