【问题标题】:Inheritance with Spring Java Config uses different beanSpring Java Config 的继承使用不同的 bean
【发布时间】:2015-12-22 21:23:19
【问题描述】:

以下示例显示了使用 spring java config 显式连接依赖项,这会导致在使用 spring 配置类的接口时连接不同的 bean。

这似乎不应该发生,或者至少给出正常警告,即有两个 bean 作为自动装配的候选者并且它不知道选择哪个。

对这个问题有什么想法吗?我的猜测是配置类之间没有真正的名称间距,正如语法“this.iConfig.a()”所暗示的那样,这是否可以被视为一个错误(如果只是为了不警告关于 2 个候选 bean)?

public class Main
{
    public static void main( final String[] args )
    {
        final ApplicationContext context = new AnnotationConfigApplicationContext( IConfigImpl.class, ServiceConfig.class );
        final Test test = context.getBean( Test.class );

        System.out.println( test );
    }
}

public class Test
{
    private final String string;

    public Test( final String param )
    {
        this.string = param;
    }

    public String toString()
    {
        return this.string;
    }
}

@Configuration
public interface IConfig
{
    @Bean
    public String a();
}

@Configuration
public class IConfigImpl implements IConfig
{
    @Bean
    public String a()
    {
        return "GOOD String";
    }
}

@Configuration
public class ServiceConfig
{
    @Autowired
    IConfig iConfig;

    @Bean
    Test test()
    {
        return new Test( this.iConfig.a() );
    }

    @Bean
    String a()
    {
        return "BAD String";
    }
}

在这种情况下,我希望“GOOD String”始终被连接到 Test 对象中,但是在上下文加载器中翻转 IConfigImpl.class、ServiceConfig.class 的顺序会改变加载哪个字符串。

使用 Spring 4.0.7 测试

编辑:进一步的测试表明这与固有配置无关。如果您删除 IConfig 接口,结果相同。

【问题讨论】:

    标签: java spring


    【解决方案1】:

    我相信这是 Spring 多年来的一种行为。

    如果你重新定义一个 bean,最后加载的那个会胜出。

    另一个问题是在使用 java 配置时如何控制 bean 加载的顺序。查看这篇文章http://www.java-allandsundry.com/2013/04/spring-beans-with-same-name-and.html,它向您展示了如何使用其他 Spring java 配置的@Import 进行排序。

    解决方案实际上很简单 - 如果您需要覆盖以前的 定义的bean(不用说自动装配的灵活性) 不同的 bean 名称),两者都使用 XML bean 配置 被覆盖的bean和覆盖bean或使用 @配置。 XML bean 配置是本文的第一个示例 条目,带有@Configuration 的条目将是这样的:

    @Configuration
    public class Context1JavaConfig {
        @Bean
        public MemberService memberService() {
            return new MemberSvcImpl1();
        }
    }
    
    @Configuration
    @Import(Context1JavaConfig.class)
    public class Context2JavaConfig {
        @Bean
        public MemberService memberService() {
            return new MemberSvcImpl2();
        }
    }
    

    【讨论】:

    • 覆盖同名 bean 是有道理的,但在这种情况下,我专门引用 iConfig 配置中指定的 bean。我希望得到那里指定的那个。
    【解决方案2】:

    Stepan提到了订单问题。以下是关于your comment他们的回答

    覆盖同名的bean是有道理的,但在这种情况下,我是 专门引用 iConfig 中指定的 bean 配置。我希望得到那里指定的那个。

    为了实现@Configuration和bean的缓存,这样调用就好了

    @Configuration
    class Example {
        @Bean
        public UncaughtExceptionHandler uncaughtExceptionHandler() {
            return (thread, throwable) -> System.out.println(thread + " => " + throwable.getMessage());
        }
    
        @Bean
        @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        public Thread newThread() {
            Thread thread = new Thread();
            thread.setUncaughtExceptionHandler(uncaughtExceptionHandler()); // <<<<<< allowing this
            return thread;
        }
    }
    

    Spring 实际上使用 CGLIB 来创建 @Configuration 注释类的代理子类型。此代理维护对支持 ApplicationContext 的引用并使用它来解析 bean。

    所以你的例子中的调用

    return new Test(this.iConfig.a());
    

    并没有真正调用IConfigImpl#a()。它从代理拦截器调用this code(从 4.2 开始)。代码使用对应的Method确定目标bean名称,使用ApplicationContextBeanFactory解析bean。由于名为 a 的 bean 的 bean 定义已被覆盖,因此将使用新的 bean 定义。该 bean 定义使用 ServiceConfig#a() 方法作为其工厂方法。

    这在文档中有所描述,here

    所有@Configuration 类在启动时使用 CGLIB 进行子类化。 在子类中,子方法首先检查容器是否有任何 在调用父方法并创建一个之前缓存的(作用域)bean 新实例。


    这可以被认为是一个错误[...]?

    我不这么认为。该行为已记录在案。

    【讨论】:

    • 谢谢,内部实现细节清楚了。我不能 100% 确定 this.iConfig.a() 不会给我那个 bean,这似乎仍然是一种耻辱,但我明白为什么必须这样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-27
    相关资源
    最近更新 更多