我在Spring 3中越来越多地使用新的@Bean
配置样式,作为XML bean定义文件的一种更安全的替代方法。但有时候,由于Java缺乏类型表达性和Spring作用域代理,这种类型安全性可能会阻止你做有效的事情。
演示问题的完整单元测试如下,但简单地说我有一个类ServiceBean
,它实现了接口ServiceA
和ServiceB
。这个bean是一个作用域代理(在这种情况下是会话范围的)。我还有bean ClientA
和ClientB
,它们分别注入了ServiceA
和ServiceB
类型的对象。
在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代理(实现ServiceA
和ServiceB
)和ServiceBean
对象之间的冲突。
我尝试过使用泛型:
@Bean @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public <T extends ServiceA & ServiceB> T services() {
return (T)new ServiceBean();
}
但这甚至都没有编译。
我认为这不是一种特别奇特的情况,而且之前我已经遇到过几次。在过去,解决方法是使用TARGET_CLASS
代理而不是接口代理,但这不是我的选择。
有人能弄清楚如何使这项工作吗?
答案 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());
}