【问题标题】:Behaviour of lite @Bean methods in Spring 5Spring 5 中 lite @Bean 方法的行为
【发布时间】:2018-06-27 18:57:05
【问题描述】:

来自Spring 5 docs

当@Bean 方法在未注解的类中声明时 使用@Configuration,它们被称为在 “精简”模式。在 @Component 甚至在普通中声明的 Bean 方法 旧班级将被视为“精简版”,具有不同的主要目的 包含类和 @Bean 方法只是一种奖励 那里。例如,服务组件可以将管理视图暴露给 容器通过每个适用的附加 @Bean 方法 组件类。在这种情况下,@Bean 方法很简单 通用工厂方法机制。

与完整的@Configuration 不同,精简的@Bean 方法不能声明 豆间依赖关系。相反,它们在其包含操作 组件的内部状态和可选的参数,它们可能 宣布。 因此这种@Bean 方法不应调用其他@Bean 方法;每个这样的方法实际上只是一个工厂方法 特定的 bean 引用,没有任何特殊的运行时语义。 这里的积极副作用是不需要 CGLIB 子类化 在运行时应用,因此在类方面没有限制 设计(即包含类可能仍然是最终的等)。

处理常规 Spring 组件中的 @Bean 方法 与 Spring @Configuration 中的对应项不同 班级。 不同之处在于@Component 类没有增强 CGLIB 拦截方法和字段的调用。 CGLIB 代理是在@Bean 中调用方法或字段的方法 @Configuration 类中的方法创建 bean 元数据引用 合作对象; 普通 Java 不会调用此类方法 语义,而是通过容器以提供 Spring bean 的常规生命周期管理和代理,即使在 通过对 @Bean 方法的编程调用来引用其他 bean。 在 相反,在普通的 @Bean 方法中调用方法或字段 @Component 类具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他应用约束。

我原以为下面的代码会抛出一个异常/bean1.bean2 为 null 并且不会执行 init 方法。但是,下面的代码运行良好并打印:

Should never be invoked
Expected null but is ch.litebeans.Bean2@402bba4f

所以在我看来,lite bean 的行为与从 @Configuration 注释类构造的 bean 相同。有人能指出在哪种情况下不是这种情况吗?

.

package ch.litebeans;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        Bean1 bean1 = ctx.getBean(Bean1.class);
        System.out.println("Expected null but is " + bean1.getBean2());
    }
}

package ch.litebeans;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"ch.litebeans"})
public class ApplicationConfig {}

package ch.litebeans;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class Factory1 {
    @Bean
    public Bean1 getBean1(Bean2 bean2){
        return new Bean1(bean2);
    }
}

package ch.litebeans;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class Factory2 {
    @Bean(initMethod = "init")
    public Bean2 getBean2(){
        return new Bean2();
    }
}

package ch.litebeans;

public class Bean1 {
    private Bean2 bean2;
    public Bean1(Bean2 bean2){
        this.bean2 = bean2;
    }
    public Bean2 getBean2(){
        return bean2;
    }
}

package ch.litebeans;

public class Bean2 {
    public void init(){
        System.out.println("Should never be invoked");
    }
}

编辑:

根据 Mike Hill 的解释,我添加了一个示例来展示差异:

public class BeanLiteRunner {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyComponent.class,
                MyConfiguration.class);
        MyComponent.MyComponentBean1 componentBean1 = acac.getBean(MyComponent.MyComponentBean1.class);
        MyComponent.MyComponentBean1 componentBean2 = acac.getBean(MyComponent.MyComponentBean1.class);

        MyConfiguration.MyConfigurationBean1 configurationBean1 = acac.getBean(MyConfiguration
                .MyConfigurationBean1.class);
        MyConfiguration.MyConfigurationBean1 configurationBean2 = acac.getBean(MyConfiguration
                .MyConfigurationBean1.class);
    }
}

@Component
public class MyComponent {

    @Bean
    public MyComponent.MyComponentBean1 getMyComponentBean1(){
        return new MyComponent.MyComponentBean1(getMyComponentBean2());
    }

    @Bean
    public MyComponent.MyComponentBean2 getMyComponentBean2(){
        return new MyComponent.MyComponentBean2();
    }


    public static class MyComponentBean1{
        public MyComponentBean1(MyComponent.MyComponentBean2 myComponentBean2){

        }
    }

    public static class MyComponentBean2{
        public MyComponentBean2(){
            System.out.println("Creating MyComponentBean2");
        }
    }
}

@Configuration
public class MyConfiguration {
    @Bean
    public MyConfigurationBean1 getMyConfigurationBean1(){
       return new MyConfigurationBean1(getMyConfigrationBean2());
    }

    @Bean
    public MyConfigurationBean2 getMyConfigrationBean2(){
        return new MyConfigurationBean2();
    }


    public static class MyConfigurationBean1{
        public MyConfigurationBean1(MyConfigurationBean2 myConfigurationBean2){}
    }

    public static class MyConfigurationBean2{
        public MyConfigurationBean2(){
            System.out.println("Creating MyConfigrationBean2");
        }
    }
}

输出符合预期

> Creating MyComponentBean2 
> Creating MyComponentBean2 
> Creating MyConfigrationBean2

【问题讨论】:

  • 尝试将 getter getBean2() 重命名为 Bean1 类中的其他名称。
  • @NiVeR 谢谢,我刚试了一下,没什么区别。
  • @Pavel:这个问题在这里不适用,因为 OP 正在询问 Spring 5 中的一个新功能。
  • 它在 Spring 3.0 中是否正常工作?这个功能是3.0版本开始的,可能是5.0版本的bug?

标签: java spring


【解决方案1】:

这不是错误。在组件扫描期间,Spring 的 lite bean 定义会自动添加到上下文中。使用工厂方法的 Bean 定义(即所有 @Bean 定义的 bean)将自动尝试使用当前上下文自动装配参数。详情请见ConstructorResolver#instantiateUsingFactoryMethod

引用的文档可能并不完全清楚。 “lite”和“full”bean 配置之间的主要区别严格来说是在“full”(@Configuration)模式下完成的代理。举个例子,如果我们在 @Configuration 类中定义我们的 bean 会发生什么:

@Configuration
public MyConfiguration {

    @Bean
    public Bean1 bean1() {
        return new Bean1(bean2());
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

bean1() 中调用bean2() 实际上将引用上下文中的singleton bean2 实例,而不是直接调用已实现的bean2() 方法。事实上,在该配置类中调用多少bean2() 方法并不重要——真正的bean2 方法只会被执行一次。

“Lite”模式不代理这些方法,这意味着每个bean2() 方法调用实际上将直接引用方法实现并且将返回上下文中的bean。相反,它将创建一个不受上下文跟踪的新的单独实例(这是默认的 Java 行为)。

【讨论】:

  • 谢谢,迈克。我现在的问题是 @Configuration 如何在内部工作? @Configuration 使用 CGLIB 进行代理,但 CGLIB 不支持“自调用”(因为它使用运行时编织)。自我调用的调用怎么可能被截获?当时唯一合理的解释是spring本身内部使用AspectJ进行编译时织入,但似乎并非如此。
  • 我不知道那个 cglib 约束。你确定它适用于cglib吗?我只在 Spring AOP 代理的文档中看到它。不太熟悉 AspectJ/ASM/cglib/etc 的实现。看起来 Spring 正在使用 cglib 生成具有覆盖方法的目标类型的子类,这似乎没有自调用问题:ConfigurationClassEnhancer$BeanMethodInterceptor
  • 我会说我有 90% 的把握。看看这个:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-understanding-aop-proxiesthis https://stackoverflow.com/questions/42921388/same-class-invoke-not-effective-in-spring-aop-cglib。我问的原因是要完全理解这个概念。如果我错了,请纠正我。
  • @skryvets 看起来这不是 cglib 的限制,而是 Spring 选择使用 cglib 的方式。关键似乎在 ConfigurationClassEnhancer 实现中,但是如果您正在寻找更多信息,那么具有更多 Spring/cglib 知识的人必须插话。
猜你喜欢
  • 1970-01-01
  • 2016-01-29
  • 2017-02-02
  • 1970-01-01
  • 2015-05-28
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
相关资源
最近更新 更多