Spring @Bean配置和java多态

时间:2010-10-27 13:00:52

标签: java spring

我在Spring 3中越来越多地使用新的@Bean配置样式,作为XML bean定义文件的一种更安全的替代方法。但有时候,由于Java缺乏类型表达性和Spring作用域代理,这种类型安全性可能会阻止你做有效的事情。

演示问题的完整单元测试如下,但简单地说我有一个类ServiceBean,它实现了接口ServiceAServiceB。这个bean是一个作用域代理(在这种情况下是会话范围的)。我还有bean ClientAClientB,它们分别注入了ServiceAServiceB类型的对象。

在Spring XML配置中,没有问题。 Spring为ServiceBean生成JDK代理,它实现了两个接口,并且都注入到客户端bean中。它都是反射性的,运行时类型也很好。

尝试使用@Bean样式,但是你遇到了问题。这是示范性测试。

首先,服务:

public interface ServiceA {}

public interface ServiceB {}

public class ServiceBean implements ServiceA, ServiceB {}

现在,客户:

public class ClientA {
    public ClientA(ServiceA service) {}
}    

public class ClientB {
    public ClientB(ServiceB service) {}
}

现在,Spring bean定义:

@Configuration
public class ScopedProxyConfig {

    @Bean @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
    public ServiceBean services() {
        return new ServiceBean();
    }

    @Bean
    public ClientA clientA() {
        return new ClientA(services());
    }

    @Bean
    public ClientB clientB() {
        return new ClientB(services());
    }
}

最后,单元测试和支持上下文:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ScopedProxyTest {

    private @Resource ClientA clientA;
    private @Resource ClientB clientB;      

    public @Test void test() {
        assertThat(clientA, is(notNullValue()));
        assertThat(clientB, is(notNullValue()));
    }
}

<beans>            
    <context:annotation-config/>
    <bean class="test.ScopedProxyConfig"/>      
</beans>

(为清晰起见,省略了XML名称空间)。

这一切都编译得很好。但是,运行测试会得到类型转换运行时异常:

  

引起:java.lang.ClassCastException:$ Proxy11无法强制转换为test.ServiceBean       at test.ScopedProxyConfig $$ EnhancerByCGLIB $$ d293ecc3.services()       在test.ScopedProxyConfig.clientA(ScopedProxyConfig.java:26)

我不清楚这是告诉我的,但它似乎是JDK代理(实现ServiceAServiceB)和ServiceBean对象之间的冲突。

我尝试过使用泛型:

@Bean @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public <T extends ServiceA & ServiceB> T services() {
    return (T)new ServiceBean();
}

但这甚至都没有编译。

我认为这不是一种特别奇特的情况,而且之前我已经遇到过几次。在过去,解决方法是使用TARGET_CLASS代理而不是接口代理,但这不是我的选择。

有人能弄清楚如何使这项工作吗?

2 个答案:

答案 0 :(得分:1)

我认为您必须采用更基于界面的解决方案:

创建一个界面ServiceC

public interface ServiceC extends ServiceA, ServiceB {}

ServiceBean实现该接口

public class ServiceBean implements ServiceC{}

在您的ScopedProxyConfig

@Bean @Scope(value=WebApplicationContext.SCOPE_SESSION,
             proxyMode=ScopedProxyMode.INTERFACES)
public ServiceC services() {
    return new ServiceBean();
}

一致地使用接口应该让Spring使用JDK代理。

答案 1 :(得分:1)

这个至少可以编译,也许它会起作用:

@Bean @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES) 
public <T extends ServiceA & ServiceB> T services() { 
    return (T)new ServiceBean(); 
}

@Bean 
public ClientA clientA() { 
    return new ClientA(this.<ServiceBean>services()); 
} 

@Bean 
public ClientB clientB() { 
    return new ClientB(this.<ServiceBean>services()); 
}