3.10.5 在组件中定义Bean元数据
Spring组件也可以向容器提供bean定义元数据。 在@Configuration
注解的类中使用@Bean
注解定义bean元数据(也就是Spring bean)。 这里有一个简单的例子:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
这个类是一个Spring组件有个方法doWork()
。然而,它还有一个工厂方法publicInstance()
,用于产生一个bean定义。@Bean
注解了工厂方法,还设置了其他的bean定义的属性,例如通过@Qualifier
注解的qualifier值。 可以指定的其他方法级别注解是 @Scope
, @Lazy
以及自定义 qualifier注解。
除了用于组件初始化的角色之外,@Lazy 注解也可以在@Autowired 或者@Inject 处使用。这种情况下,该注入将会变成延迟注入代理lazy-resolution proxy(也就是懒加载). |
自动注入的字段和方法也可以像前面讨论的一样被支持,也可以支持@Bean方法的自动注入:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
样例中,自动注入的方法参数,类型String
,名称为country
,将会被设置为另一个实例privateInstance
的Age
属性。 Spring Expression Language元素通过符号#{<expression>}
定义属性的值。对于@Value
注解,表达式解析器在解析表达式后,会查找bean的名字并设置value。
从Spring Framework 4.3开始,你还可以声明一个类型为“InjectionPoint”(或其更具体的子类“DependencyDescriptor”)的工厂方法参数,以访问触发创建当前bean的请求注入点。请注意,这仅适用于实际创建bean实例,而不适用于注入现有实例。因此,这个特性对prototype scope的bean最有意义。对于其他scope,工厂方法将只能看到触发在给定scope中创建新bean实例的注入点:例如,触发创建一个延迟单例bean的依赖。在这种情况下,使用提供的注入点元数据具有语义关怀(为程序员考虑提供便利)。
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
在Spring component中处理@Bean
和在@Configuration
中处理是不一样的。区别在于,在@Component
中,不会使用CGLIB
增强去拦截方法和属性的调用。在@Configuration
注解的类中,@Bean
注解的方法创建的bean对象的方法和属性的调用,是使用CGLIB
代理。方法的调用不是常规的java语法,而是通过容器来提供通常的生命周期管理和代理Spring bean,甚至在通过编程地方式调用@Bean方法时也会形成对其它bean的引用。 相比之下,在一个简单的“@Component”类中调用@Bean
方法中的方法或字段具有标准Java语义,没有应用特殊的CGLIB处理或其他约束。
你可以将@Bean 方法声明为static ,允许在不将其包含的配置类作为实例的情况下调用它们。这在定义后置处理器bean时是特别有意义的。比如BeanFactoryPostProcessor或BeanPostProcessor,因为这类bean会在容器的生命周期前期被初始化,而不会触发其它部分的配置。对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration 类内部。这是由于技术限制CGLIB的子类代理只会重写非静态方法。因此,对另一个@Bean方法的直接调用只有标准的Java语法,只会从工厂方法本身直接返回一个独立的实例。由于Java语言的可见性,@Bean方法并不一定会对容器中的bean有效。你可能很随意的在非 @Configuration 类中定义或定义为静态方法。然而,在@Configuration 类中的正常的@Bean方法都需要被重写的,因此,它们不应该定义为private或final。 @Bean方法也可以在父类中被发现,同样适用于Java 8中接口的默认方法。这使得组建复杂的配置时能具有更好的灵活性,甚至可能通过Java 8的默认方法实现多重继承,这在Spring 4.2开始支持。最后,请注意,单个类可以为同一个bean保存多个 @Bean 方法,如根据运行时可用的依赖关系选择合适的工厂方法。使用的算法时选择“最贪婪”的构造方法,一些场景可能会按如下方法选择相应的工厂方法:满足最多依赖的会被选择,这与使用@Autowired时选择多个构造方法时类似. |