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注解。

[Tip]
除了用于组件初始化的角色之外,@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,将会被设置为另一个实例privateInstanceAge属性。 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处理或其他约束。

[Note]
你可以将@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时选择多个构造方法时类似.

results matching ""

    No results matching ""