Spring 组件还可以向容器提供 bean 定义元数据。您可以使用与在 @Configuration 注解类中定义 bean 元数据相同的 @Bean 注解来实现这一点。下面的例子说明了如何这样做:

    1. @Component
    2. public class FactoryMethodComponent {
    3. @Bean
    4. @Qualifier("public")
    5. public TestBean publicInstance() {
    6. return new TestBean("publicInstance");
    7. }
    8. public void doWork() {
    9. // 省略了组件方法实现
    10. }
    11. }

    前面的类是一个 Spring 组件,在它的 doWork()方法里有特定的应用代码。然而,它也贡献了一个Bean 定义,它有一个工厂方法,引用了 publicInstance() 方法。@Bean 注解标识了工厂方法和其他 Bean 定义属性,例如通过 @Qualifier注解标识了限定值。其他可以指定的方法级注解有 @Scope@Lazy 和自定义限定符注解。

    :::tips 除了在组件初始化中的作用外,你还可以将 @Lazy 注解放在标有 @Autowired 或 @Inject 的注入点上。在这种情况下,它导致了一个懒加载解决的代理的注入。然而,这种代理方法是相当有限的。对于复杂的懒加载交互,特别是与可选的依赖关系相结合,我们推荐使用 ObjectProvider<MyTargetBean>来代替。 :::

    正如前面讨论的,自动装配字段和方法支持 @Bean 方法的自动装配。下面的例子说明了如何这样做:

    1. @Component
    2. public class FactoryMethodComponent {
    3. private static int i;
    4. @Bean
    5. @Qualifier("public")
    6. public TestBean publicInstance() {
    7. return new TestBean("publicInstance");
    8. }
    9. // 使用自定义限定符和方法参数
    10. @Bean
    11. protected TestBean protectedInstance(
    12. @Qualifier("public") TestBean spouse,
    13. @Value("#{privateInstance.age}") String country) {
    14. TestBean tb = new TestBean("protectedInstance", 1);
    15. tb.setSpouse(spouse);
    16. tb.setCountry(country);
    17. return tb;
    18. }
    19. @Bean
    20. private TestBean privateInstance() {
    21. return new TestBean("privateInstance", i++);
    22. }
    23. @Bean
    24. @RequestScope
    25. public TestBean requestScopedInstance() {
    26. return new TestBean("requestScopedInstance", 3);
    27. }
    28. }

    这个例子的 protectedInstance 方法上的 country 参数, 自动装配到另一个名为 privateInstance 的 bean 上的 age 属性值(简单说就是注入了 privateInstance() 方法产生的对象中的 age 属性的值)。一个 Spring 表达式语言元素通过符号 #{ <表达式> }定义了该属性的值。对于 @Value注解,表达式解析器被预设为在解析表达式文本时寻找 Bean 名称。

    从 Spring Framework 4.3 开始,你也可以声明一个 InjectionPoint(或其更具体的子类:DependencyDescriptor)类型的工厂方法参数来访问触发创建当前 Bean 的请求注入点。请注意,这只适用于 Bean 实例的实际创建,而不适用于现有实例的注入。因此,这个功能对多例范围的 Bean 最有意义。对于其它作用域,工厂方法只看到在给定作用域中触发创建新 bean 实例的注入点(例如,触发创建 lazy singleton bean 的依赖关系)。在这种情况下,你可以在语义上注意使用所提供的注入点元数据。下面的例子显示了如何使用 InjectionPoint。

    1. @Component
    2. public class FactoryMethodComponent {
    3. @Bean @Scope("prototype")
    4. public TestBean prototypeInstance(InjectionPoint injectionPoint) {
    5. return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    6. }
    7. }
    8. // 这个例子笔者没有运行成功,应该还缺少什么上下文,注入不进来 InjectionPoint,不知道是否需要自己处理 InjectionPoint ?

    普通 Spring 组件中的 @Bean 方法与 Spring @Configuration 类中的对应方法的处理方式不同。区别在于,@component 类没有用 CGLIB 来拦截方法和字段的调用。CGLIB 代理是调用 @Configuration 类中@Bean 方法中的方法或字段的方式,它创建了对协作对象的 Bean 元数据引用。这样的方法不是用正常的 Java 语义来调用的,而是通过容器,以便提供 Spring Bean 通常的生命周期管理和代理,即使是在通过编程调用 @Bean 方法来引用其他 Bean 的时候。相比之下,在普通的 @Component 类中调用 @Bean 方法中的方法或字段具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他约束条件适用。 :::tips 上面的描述笔者不是很明白,但是有一个描述或许可以解释在 @Configuration 中的直接调用 @Bean 的方法,返回的居然是从容器中获取的 bean 实例。比如
    @Configuration
    publlic class Demo{
    @Bean
    publ DataSource dataSource(){
    return new DataSource();
    }

    @Bean
    public XXX xxx(){
    this.dataSource() // 这里调用,返回的不是这里表面看到的直接 new 了一个,而是从容器中获取的实例
    }
    }
    就是上面描述的,在 @Configuration 中的 @Bean 方法是被 CGLIB 代理了的 :::

    :::info 你可以将 @Bean 方法声明为静态的,这样就可以在不创建其包含的配置类实例的情况下调用它们。在定义 post-processor Bean(例如,BeanFactoryPostProcessor 或 BeanPostProcessor 类型)时,这一点特别有意义,因为这种 bean 在容器生命周期的早期被初始化,并且应该避免在这一点上触发配置的其他部分。

    由于技术上的限制,对静态 @Bean 方法的调用永远不会被容器拦截,甚至在 @Configuration 类中也不会(如本节前面所述)。CGLIB 子类只能覆盖非静态方法。因此,对另一个 @Bean 方法的直接调用具有标准的 Java 语义,结果是直接从工厂方法本身返回一个独立实例。

    @Bean 方法的 Java 语言可见性对 Spring 容器中产生的 Bean 定义没有直接影响。你可以在非@Configuration 类中自由地声明你的工厂方法,也可以在任何地方为静态方法声明。然而,@Configuration 类中的常规 @Bean 方法需要是可重写的—也就是说,它们不能被声明为 pricate 或则 final。

    @Bean 方法也可以在特定组件或配置类的基类上发现,也可以在组件或配置类实现的接口中声明的Java 8 默认方法上发现。这使得组成复杂的配置安排有了很大的灵活性,从 Spring 4.2 开始,甚至可以通过 Java 8 默认方法进行多重继承。

    最后,一个类可以为同一个 Bean 持有多个 @Bean 方法,作为对多个工厂方法的安排,根据运行时的可用依赖关系来使用。这与在其他配置情况下选择 “最环保 “的构造函数或工厂方法的算法相同。在构造时选择具有最大数量的可满足的依赖关系的变量,类似于容器在多个 @Autowired 构造函数之间进行选择。 :::