6.1.1 在组件中定义bean的元数据
Spring组件还可以向容器提供bean的定义元数据。在@Configuration带注释的类中,您可以使用相同的@Bean注解来定义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注解指定的限定符值。可以指定的其他方法级别注解是@ Scope、@ Lazy以及自定义限定符注解。
除了它的组件初始化角色之外,@ Lazy注解也可以放在标有@Autowired或@Inject的注入点上。在这种情况下,它会引起惰性解析代理的注入。
如前所述,支持自动装配的字段和方法,也支持@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 @Scope(BeanDefinition.SCOPE_SINGLETON) private TestBean privateInstance() { return new TestBean("privateInstance", i++); } @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public TestBean requestScopedInstance() { return new TestBean("requestScopedInstance", 3); } }
该示例将String方法参数country自动装配到另一个名为privateInstance的bean上的Age属性的值。Spring表达式语言元素通过符号#{<expression>}来定义属性的值。对于@Value注解,在解析表达式文本时,表达式解析器会预先配置为寻找bean名称。
在Spring组件中的@Bean方法的处理方式相对于在Spring @Configuration类中是不同的。不同之处在于,使用CGLIB不会增强@Component类来拦截方法和字段的调用。CGLIB代理是调用@Configuration类中的@Bean方法中的方法或字段创建对协作对象的bean元数据引用的方法; 这些方法不是用普通的Java语义调用的,而是通过容器调用的,为了提供正常的生命周期管理和Spring bean的代理,即使在通过对@Bean方法的编程调用引用其他bean时也是如此。相反,在普通@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不会有特殊的CGLIB处理或其他约束。
您可以将@Bean方法声明为static,允许在不创建包含配置类作为实例的情况下调用它们。这在定义后置处理器的bean时特别有意义,例如,类型为BeanFactoryPostProcessor或BeanPostProcessor,因为这些bean将在容器生命周期的早期初始化,并且应该避免在此时触发配置的其他部分。
请注意,对静态@Bean方法的调用永远不会被容器拦截,甚至在@Configuration类中也不会被拦截(参见上文)。这是由于技术限制:CGLIB子类化只能覆盖非静态方法。因此,直接调用另一个@Bean方法将具有标准的Java语义,从而导致直接从工厂方法本身返回一个独立的实例。@Bean方法的Java语言可见性对Spring容器中生成的bean定义没有直接的影响。 您可以自由地声明您认为适合非@Configuration类的工厂方法以及任何地方的静态方法。 但是,@ Consfiguration类中的常规@Bean方法要求是可覆盖的,即它们不能声明为private或final。还将在给定组件或配置类的基类以及由组件或配置类实现的接口中声明的Java 8缺省方法上发现@Bean方法。这使得在编写复杂的配置布置时具有很大的灵活性,从Spring 4.2开始,甚至可以通过Java 8默认方法实现多重继承。
最后,请注意,单个类可以为同一个bean保存多个@Bean方法,作为多个工厂方法的布置,具体取决于运行时的可用依赖项。 这与在其他配置方案中选择“最贪婪”构造函数或工厂方法的算法相同:将在构造时选择具有最多可满足依赖项的变体,类似于容器在多个@Autowired构造函数之间如何选择的方式。