Spring JUnit4 手动/自动装配困境
我遇到了一个问题,这个问题只能用我对 Spring 的 IoC 容器设施和上下文设置的根本缺乏理解来解释,所以我要求对此进行澄清。
仅供参考,我维护的应用程序具有以下技术堆栈:
- Java 1.6
- Spring 2.5.6
- RichFaces 3.3.1-GA UI
- Spring 框架用于 bean 管理,Spring JDBC 模块用于 DAO 支持
- Maven 用作构建管理器
- JUnit 4.4 现在被引入作为测试引擎
我正在追溯(原文如此!)为应用程序编写 JUnit 测试,令我惊讶的是,我无法通过使用 setter 注入将 bean 注入到测试类中,而不求助于 @Autowire 表示法。
让我提供一个设置示例和随附的配置文件。
测试类 TypeTest
非常简单:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
@Autowired
private IType type;
@Test
public void testFindAllTypes() {
List<Type> result;
try {
result = type.findAlltTypes();
assertNotNull(result);
} catch (Exception e) {
e.printStackTrace();
fail("Exception caught with " + e.getMessage());
}
}
}
它的上下文在 TestStackOverflowExample-context.xml
中定义:
<context:property-placeholder location="classpath:testContext.properties" />
<context:annotation-config />
<tx:annotation-driven />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.connection.driver.class}" />
<property name="url" value="${db.connection.url}" />
<property name="username" value="${db.connection.username}" />
<property name="password" value="${db.connection.password}" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="beanDAO" class="com.example.BeanDAOImpl">
<property name="ds" ref="dataSource"></property>
<property name="beanDAOTwo" ref="beanDAOTwo"></property>
</bean>
<bean id="beanDAOTwo" class="com.example.BeanDAOTwoImpl">
<property name="ds" ref="dataSource"></property>
</bean>
<bean id="type" class="com.example.TypeImpl">
<property name="beanDAO" ref="beanDAO"></property>
</bean>
TestContext.properties
位于类路径中,仅包含数据源所需的特定于数据库的数据。
这就像一个魅力,但我的问题是 - 为什么当我尝试手动连接 bean 并执行 setter 注入时它不起作用,如下所示:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
private IType type;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
我在这里缺少什么?这里的哪一部分配置是错误的?当我尝试通过 setter 手动注入 bean 时,测试失败,因为这部分
result = type.findAlltTypes();
在运行时被解析为 null。当然,我查阅了 Spring 参考手册并尝试了 XML 配置的各种组合;我能得出的结论是 Spring 无法注入 beans,因为它以某种方式无法正确取消引用 Spring Test Context 引用,但是通过使用 @Autowired 这会“自动”发生,我真的不明白为什么会这样,因为两个 的 JavaDoc Autowired
注释及其 PostProcessor
类没有提到这一点。
另外值得补充的是,@Autowired
仅在此处的应用程序中使用。在其他地方仅执行手动接线,因此这也带来了问题 - 为什么在我的测试中它在那里工作,而不是在这里?我缺少 DI 配置的哪一部分? @Autowired
如何获取 Spring Context 的引用?
编辑: 我也尝试过这个,但结果相同:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
也许还有其他想法吗?
编辑2: 我找到了一种无需编写自己的 TestContextListener 或 BeanPostProcessor 的方法。它非常简单,事实证明我上次编辑的方向是正确的:
1)基于构造函数的上下文解析:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
type = ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
2)通过实现 ApplicationContextAware 接口:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
type = (Type) ctx.getBean("type");
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
这两种方法都正确实例化了 bean。
I ran into an issue that can only be explained with my fundamental lack of understanding of Spring's IoC container facilities and context setup, so I would ask for clarification regarding this.
Just for reference, an application I am maintaing has the following stack of technologies:
- Java 1.6
- Spring 2.5.6
- RichFaces 3.3.1-GA UI
- Spring framework is used for bean management with Spring JDBC module used for DAO support
- Maven is used as build manager
- JUnit 4.4 is now introduced as test engine
I am retroactively (sic!) writing JUnit tests for the application and what suprised me is that I wasn't able to inject a bean into a test class by using setter injection without resorting to @Autowire notation.
Let me provide set up an example and accompanying configuration files.
The test class TypeTest
is really simple:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
@Autowired
private IType type;
@Test
public void testFindAllTypes() {
List<Type> result;
try {
result = type.findAlltTypes();
assertNotNull(result);
} catch (Exception e) {
e.printStackTrace();
fail("Exception caught with " + e.getMessage());
}
}
}
Its context is defined in TestStackOverflowExample-context.xml
:
<context:property-placeholder location="classpath:testContext.properties" />
<context:annotation-config />
<tx:annotation-driven />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.connection.driver.class}" />
<property name="url" value="${db.connection.url}" />
<property name="username" value="${db.connection.username}" />
<property name="password" value="${db.connection.password}" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="beanDAO" class="com.example.BeanDAOImpl">
<property name="ds" ref="dataSource"></property>
<property name="beanDAOTwo" ref="beanDAOTwo"></property>
</bean>
<bean id="beanDAOTwo" class="com.example.BeanDAOTwoImpl">
<property name="ds" ref="dataSource"></property>
</bean>
<bean id="type" class="com.example.TypeImpl">
<property name="beanDAO" ref="beanDAO"></property>
</bean>
TestContext.properties
is in classpath and contains only db-specific data needed for datasource.
This works like a charm but my question is - why doesn't it work when I try to manually wire beans and perform setter injection as in:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
private IType type;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
What am I missing here? What part of configuration is wrong here? When I try to manually inject beans via setters, test fails because this part
result = type.findAlltTypes();
is resolved as null in runtime. I've, of course, consulted the Spring reference manual and tried various combinations of XML configuration; all I could conclude is that Spring was unable to inject beans because it somehow fails to properly dereference Spring Test Context reference but by using @Autowired this happens "automagically" and I really can't see why is that because JavaDoc of both Autowired
annotation and its PostProcessor
class doesn't mention this.
Also worth adding is the fact that @Autowired
is used in application only here. Elsewhere only manual wiring is performed, so this also brings forth question - why is it working there and not here, in my test? What part of DI configuration am I missing? How does @Autowired
get reference of Spring Context?
EDIT:
I've also tried this but with same results:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
Any other ideas, perhaps?
EDIT2:
I've found a way without resorting to writing own TestContextListener
or BeanPostProcessor
. It is suprisingly simple and it turns out that I was on the right track with my last edit:
1) Constructor-based context resolving:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
type = ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
2) By implementing ApplicationContextAware interface:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
type = (Type) ctx.getBean("type");
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
Both of these approaches properly instanced beans.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您查看 org.springframework.test.context.support.DependencyInjectionTestExecutionListener 的源代码,您将看到以下方法(为了清晰起见,进行了格式化和注释)
:自动接线。然而,@AutoWired、@Resource等不使用自动装配机制,它们使用BeanPostProcessor。因此,当且仅当使用注释时(或者如果您注册了其他执行此操作的 BeanPostProcessor ),才会注入依赖项。
(上面的代码来自 Spring 3.0.x,但我敢打赌它在 2.5.x 中是相同的)
If you take a look at the source of
org.springframework.test.context.support.DependencyInjectionTestExecutionListener
, you will see the following method (formatted and commented for clarity):So the test object is a bean without auto-wiring. However,
@AutoWired
,@Resource
etc, don't use the autowiring mechanism, they useBeanPostProcessor
. And so the dependencies are injected if and only if the annotations are used (or if you register some otherBeanPostProcessor
that does it).(The above code is from Spring 3.0.x, but I bet it was the same in 2.5.x)