尝试编写Spring Batch单元测试时出现NoSuchBeanDefinitionException

时间:2014-04-09 05:45:03

标签: java spring unit-testing dependency-injection spring-batch

我正在尝试为我的Spring Batch作业组件编写单元测试,并说明我的处理器。根据我在网上找到的所有信息,我正确地做到了。但是当我尝试将它自动装入我的测试类时,Spring找不到我的处理器bean。

我的处理器类的名称是BatchFileRecordProcessor。我的测试类是BatchFileRecordProcessorTest。这是后者的代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/launch-context.xml"})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class})
public class BatchFileRecordProcessorTest
{
    @Autowired
    private BatchFileRecordProcessor processor = new BatchFileRecordProcessor();

    @Inject
    private BatchFileRecord batchFileRecord;

    public StepExecution getStepExecution()
    {
        StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution();
        stepExecution.getExecutionContext().putString("fileName", "part-r-00000");

        return stepExecution;
    }

    @Test
    public void process() throws Exception
    {
        batchFileRecord.setSomeField("someValue");
        batchFileRecord.setSomeOtherField("someOtherValue");

        List<SetIdentityLinkInput> inputs = processor.process(batchFileRecord);
        assertEquals(1, inputs.size());
    }
}

这是我的launch-context.xml文件,其中包含我的所有Spring配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:batch="http://www.springframework.org/schema/batch"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.mobile.identity.batch" />

    <batch:job id="IdentitySojournerLogsBatch">
        <batch:validator ref="jobParametersValidator" />
        <batch:step id="batchJob.master">
            <batch:partition step="batchJob" partitioner="partitioner">
                <batch:handler grid-size="20" task-executor="partitionTaskExecutor" />
            </batch:partition>
        </batch:step>
        <batch:listeners>
            <batch:listener ref="jobListener"/>
        </batch:listeners>
    </batch:job>

    <bean id="partitioner" class="org.springframework.batch.core.partition.support.MultiResourcePartitioner" scope="step">
        <property name="resources" value="#{jobParameters['input.file.dir']}" />
    </bean>

    <bean id="partitionTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="maxPoolSize" value="20" />
        <property name="corePoolSize" value="10" />
        <property name="queueCapacity" value="80" />
        <property name="WaitForTasksToCompleteOnShutdown" value="true" />
    </bean>

    <batch:step id="batchJob">
        <batch:tasklet>
            <batch:chunk reader="logFileReader" processor="batchFileRecordProcessor" writer="setIdentityLinkWriter" commit-interval="10" />
            <batch:listeners>
                <batch:listener ref="fileNameListener" />
                <batch:listener ref="logFileReaderListener" />
            </batch:listeners>
        </batch:tasklet>
    </batch:step>

    <bean id="logFileReaderParent" class="org.springframework.batch.item.file.FlatFileItemReader" abstract="true">
        <property name="lineMapper">
            <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
                <property name="lineTokenizer">
                    <bean class="com.mobile.identity.batch.components.AmpersandDelimitedNameValuePairTokenizer">
                        <property name="names" value="isErrorRecord,deviceId,provider,deviceType,operatingSystem,operatingSystemVersion,idfaTag,timestamp,identifierCount,identifierString" />
                    </bean>
                </property>
                <property name="fieldSetMapper">
                    <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
                        <property name="prototypeBeanName" value="batchFileRecord" />
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <bean id="logFileReader" scope="step" autowire-candidate="false" parent="logFileReaderParent">
        <property name="resource" value="#{stepExecutionContext[fileName]}" />
    </bean>

    <bean id="batchFileRecordProcessor" class="com.mobile.identity.batch.components.BatchFileRecordProcessor" autowire-candidate="true" scope="step" />

    <bean id="batchFileRecord" class="com.mobile.identity.batch.model.BatchFileRecord" scope="prototype" />
</beans>

这是我尝试运行测试类时遇到的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.mobile.identity.batch.components.BatchFileRecordProcessorTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mobile.identity.batch.components.BatchFileRecordProcessor com.mobile.identity.batch.components.BatchFileRecordProcessorTest.processor; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.mobile.identity.batch.components.BatchFileRecordProcessor] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:374)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:290)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:202)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mobile.identity.batch.components.BatchFileRecordProcessor com.mobile.identity.batch.components.BatchFileRecordProcessorTest.processor; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.mobile.identity.batch.components.BatchFileRecordProcessor] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:506)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)
    ... 29 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.mobile.identity.batch.components.BatchFileRecordProcessor] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:924)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:793)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:707)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:478)
    ... 31 more

2014/04/08 22-13-59,938:ERR:ERROR[Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@569cc9e9] to prepare test instance [com.mobile.identity.batch.components.BatchFileRecordProcessorTest@4f8c0c6b]]

[org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues threw org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.mobile.identity.batch.components.BatchFileRecordProcessorTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mobile.identity.batch.components.BatchFileRecordProcessor com.mobile.identity.batch.components.BatchFileRecordProcessorTest.processor; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.mobile.identity.batch.components.BatchFileRecordProcessor] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}]

尝试运行单元测试时,我尝试在IntelliJ的调试器中单步执行Spring代码; Spring似乎在launch-context.xml中找到了我的batchFileRecordProcessor bean定义,但由于某种原因,它并没有将它确定为我的测试类中依赖项的合适的自动轮候选。我尝试将'autowire-candidate =“true”'属性添加到launch-context.xml中的batchFileRecordProcessor bean定义,但这并没有改变输出。有点难过。

编辑:感谢您的回复。调用BatchFileRecordProcessor构造函数是一个错误,从我尝试不涉及依赖注入的替代方法时遗留下来。

这是BatchFileRecordProcessor类定义:我已经删除了(我假设)与此问题无关的所有业务逻辑方法:

/**
 *
 */
@Component("batchFileRecordProcessor")
@Scope("step")
public class BatchFileRecordProcessor implements ItemProcessor<BatchFileRecord, List<SetIdentityLinkInput>>
{
    /**
     */
    @Override
    public List<SetIdentityLinkInput> process(BatchFileRecord record) throws Exception
    {
        ...Bunch of stuff...
    }

    /**
     *
     * @param fileName
     */
    @Value("#{stepExecutionContext[fileName]}")
    public void setFileName(String fileName)
    {
        this.fileName = jobUtils.getFileNameFromFullPath(fileName);
    }
}

请注意,我确实有一个方法可以注入步骤执行参数;不确定这是否相关。

我尝试按照建议添加嵌套的aop:scoped-proxy标记,现在我得到了一个不同的例外:

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:290)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:202)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchFileRecordProcessor' defined in BeanDefinition defined in class path resource [launch-context.xml]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy22]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy22
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:527)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:567)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:103)
    at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
    at org.springframework.test.context.support.DelegatingSmartContextLoader.loadContext(DelegatingSmartContextLoader.java:228)
    at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:124)
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:148)
    ... 27 more
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.sun.proxy.$Proxy22]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy22
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:213)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:112)
    at org.springframework.aop.scope.ScopedProxyFactoryBean.setBeanFactory(ScopedProxyFactoryBean.java:109)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeAwareMethods(AbstractAutowireCapableBeanFactory.java:1475)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1443)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
    ... 40 more
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class com.sun.proxy.$Proxy22
    at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
    at net.sf.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:201)
    ... 45 more

2014/04/09 09-46-30,053:ERR:ERROR[Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@aa0399b] to prepare test instance [com.mobile.identity.batch.components.BatchFileRecordProcessorTest@1d25f490]]

[org.springframework.test.context.TestContext.getApplicationContext threw java.lang.IllegalStateException: Failed to load ApplicationContext]

3 个答案:

答案 0 :(得分:3)

这有点晚了,但不是@Autowired,请尝试使用此注释:

@Resource(name="batchFileRecordProcessor") 

这将明确指示Spring将该bean连接到您的类。从那里,您应该从Spring获得一条关于 WHY 的错误消息,它不像预期的bean。

在我的情况下,我收到了这样的错误:

Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: 
Bean named 'applicationEnrichmentProcessor' must be of type [com.xxxx.YyyyProcessor], 
but was actually of type [com.sun.proxy.$Proxy24]

在阅读Fixing BeanNotOfRequiredTypeException on Spring proxy cast on a non-singleton bean?this Spring forum post后,我发现问题已通过添加此注释得到解决:

@EnableAspectJAutoProxy(proxyTargetClass=true) 

这导致Spring的代理机制代理您的类,而不是类的接口,如Spring documentation中所述。

由于您的BatchFileRecordProcessor实现了ItemProcessor界面,我怀疑这也是您的问题。

答案 1 :(得分:1)

你已宣布

<bean id="batchFileRecordProcessor" class="com.mobile.identity.batch.components.BatchFileRecordProcessor" autowire-candidate="true" scope="step" />

由于scope,Spring会为你的bean创建一个代理并注入它。默认情况下,它将使用不适用于类类型的JDK代理,仅使用接口类型。因此,您的上下文中不存在类型为BatchFileRecordProcessor的bean,因此没有bean可用于满足@Autowired依赖项。相反,只存在一些Proxy类型的bean。

您可以强制Spring使用与类类型配合使用的CGLIB代理

<bean id="batchFileRecordProcessor" class="com.mobile.identity.batch.components.BatchFileRecordProcessor" autowire-candidate="true" scope="step">
    <aop:scoped-proxy proxy-target-class="true"/>
</bean>

我现在肯定你为什么会得到异常(我知道为什么,但我不确定如何在XML中修复它)。如果您正在使用注释,我们已经设置好了。摆脱<bean>声明并将@Component更改为

@Component("batchFileRecordProcessor")
@Scope(value = "step", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class BatchFileRecordProcessor implements ItemProcessor<BatchFileRecord, List<SetIdentityLinkInput>>
{

答案 2 :(得分:0)

我遇到了同样的问题。

使用:

解决
<bean class="org.springframework.batch.core.scope.StepScope">
    <property name="autoProxy" value="false" />
</bean>

如上所述here

我现在可以在我的测试类和

中保留@Autowired注释
@Component
@StepScope
public class CustomProcessor implements ItemProcessor<String, Void> {
    ...
}