Spring 使用 spring 引导时的批处理范围问题
Spring batch scope issue while using spring boot
我正在执行独立 spring 批处理作业。这在 JUNIT
中工作得很好
@RunWith(SpringJUnit4ClassRunner.class)
//@SpringApplicationConfiguration(classes = KPBootApplication.class)
@ContextConfiguration(locations={"classpath:kp-sb.xml"})
public class KPBootApplicationTests {
private final static Logger LOG=LoggerFactory.getLogger(KPBootApplicationTests.class);
@Autowired
ApplicationContext context;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Test
public void testJob() {
final JobParameters jobParameters = new JobParametersBuilder()
.toJobParameters();
JobExecution execution;
try {
execution = jobLauncher.run(job, jobParameters);
final ExitStatus status = execution.getExitStatus();
if (ExitStatus.COMPLETED.getExitCode().equals(status.getExitCode())) {
LOG.info("Job completed Yeaaaaaaaa!!!!!");
} else {
final List<Throwable> exceptions = execution
.getAllFailureExceptions();
for (final Throwable throwable : exceptions) {
LOG.error(throwable.getMessage(), throwable);
}
}
} catch (JobExecutionAlreadyRunningException e) {
LOG.error(e.getMessage(), e);
} catch (JobRestartException e) {
LOG.error(e.getMessage(), e);
} catch (JobInstanceAlreadyCompleteException e) {
LOG.error(e.getMessage(), e);
} catch (JobParametersInvalidException e) {
LOG.error(e.getMessage(), e);
}
}
}
和配置文件
<!-- Below code till Job Repo is commented out during spring-boot -->
<context:property-placeholder
properties-ref="kpProps" />
<util:properties id="kpProps">
<prop key="app.file.path">
D:/temp/kp1/all
</prop>
<prop key="app.max_thread_num">
10
</prop>
</util:properties>
<!--
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="batchTransactionManager" />
</bean>
-->
<bean id="batchTransactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager">
<property name="rollbackOnCommitFailure" value="false" />
</bean>
<bean id="multiResourcePartitionerReq"
class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">
<property name="resources" value="file:${app.file.path}/kp_http_request*" />
</bean>
<bean id="multiResourcePartitionerRes"
class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">
<property name="resources" value="file:${app.file.path}/kp_http_response*" />
</bean>
<bean id="kpPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
destroy-method="destroy">
<property name="maxPoolSize" value="${app.max_thread_num}" />
</bean>
<bean id="idIncrementer1"
class="org.springframework.batch.core.launch.support.RunIdIncrementer" />
<batch:job id="kpGenJob" incrementer="idIncrementer1"
job-repository="jobRepository">
<batch:split id="splitStep" next="step4">
<batch:flow>
<batch:step id="step2">
<batch:partition partitioner="multiResourcePartitionerReq"
step="step2.slave">
<batch:handler task-executor="kpPool" />
</batch:partition>
</batch:step>
</batch:flow>
<batch:flow>
<batch:step id="step3">
<batch:partition partitioner="multiResourcePartitionerRes"
step="step3.slave">
<batch:handler task-executor="kpPool" />
</batch:partition>
</batch:step>
</batch:flow>
</batch:split>
<batch:step id="step4">
<batch:tasklet transaction-manager="batchTransactionManager">
<ref bean="kptasklet" />
</batch:tasklet>
</batch:step>
</batch:job>
<batch:step id="step2.slave">
<batch:tasklet transaction-manager="batchTransactionManager">
<batch:chunk reader="reqItemReader" writer="cvsFileItemWriter"
commit-interval="10000" />
</batch:tasklet>
</batch:step>
<batch:step id="step3.slave">
<batch:tasklet transaction-manager="batchTransactionManager">
<batch:chunk reader="resItemReader" writer="cvsFileItemWriter"
commit-interval="10000" />
</batch:tasklet>
</batch:step>
<bean id="reqItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="#{stepExecutionContext['fileName']}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<!-- split it -->
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="includedFields" value="5,6,8,10,11"></property>
<property name="names"
value="f1,f2,f3,f4,f5" />
<property name="strict" value="false" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="tblHttpData" />
</bean>
</property>
</bean>
</property>
<property name="linesToSkip" value="1"></property>
</bean>
<bean id="tblHttpData" class="com.kp.batch.batch.job.domain.TblHttpData"
scope="prototype" />
<bean id="resItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="#{stepExecutionContext['fileName']}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<!-- split it -->
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="includedFields" value="3,4"></property>
<property name="names" value="f1,f2" />
<property name="strict" value="false" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="tblHttpData" />
<property name="strict" value="true" />
</bean>
</property>
</bean>
</property>
<property name="linesToSkip" value="1"></property>
</bean>
<bean id="kptasklet" class="com.kp.batch.batch.job.step.KPTasklet" />
<bean id="cvsFileItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"
scope="step">
<!-- write to this csv file -->
<property name="resource" value="#{stepExecutionContext['fileName']}.tmp" />
<property name="shouldDeleteIfExists" value="true" />
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value="," />
<property name="fieldExtractor">
<bean
class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names"
value="server,refferer,application,baseApplication,httpSize" />
</bean>
</property>
</bean>
</property>
</bean>
主要Class
@SpringBootApplication
@EnableBatchProcessing
@ImportResource(value={"classpath:spring-context.xml"})
public class KPBootApplication {
private final static Logger LOG = LoggerFactory
.getLogger(KPBootApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(
KPBootApplication.class, args);
LOG.info("Application KPBOOT Started");
SayHello hello = (SayHello) ctx.getBean("sayHello");
if (hello != null) {
LOG.debug("hello is not null");
LOG.info("Got message {}", hello.getMessage());
} else {
LOG.debug("hello is null");
}
LOG.info("Done");
}
}
当我注释掉 contextconfiguration 注释并通过启用 @SpringApplicationConfiguration 启用 spring-boot(注意:我已经使用注释 @EnableBatchProcessing 启用了 spring 批处理)并注释掉自动创建的 bean通过 spring-boot 为 spring 批处理,例如 JobRepo 我收到以下错误消息,说 step scope
没有上下文持有者
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:94)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:200)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runReflectiveCall(SpringJUnit4ClassRunner.java:252)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:254)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:217)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=15=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2': Cannot resolve reference to bean 'step2.slave' while setting bean property 'step'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2.slave': Cannot resolve reference to bean 'reqItemReader' while setting bean property 'itemReader'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1469)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:743)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:98)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86)
... 25 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2.slave': Cannot resolve reference to bean 'reqItemReader' while setting bean property 'itemReader'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1469)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 42 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:352)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 52 common frames omitted
Caused by: java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.batch.core.scope.StepScope.getContext(StepScope.java:160)
at org.springframework.batch.core.scope.StepScope.get(StepScope.java:99)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
... 54 common frames omitted
不确定为什么 spring 启动会抛出错误
注意:spring-引导版本 1.2.1
这可能是一个错误(我们仍在调查中),但我们确实有解决办法。原因是当使用 @EnableBatchProcessing
时,自动配置的 StepScope
假定 java 配置,因此不代理步骤作用域的 bean,导致它们创建得太早。解决方法是使用以下配置在 XML 配置中手动配置 StepScope
:
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="true"/>
</bean>
Michael 的评论对我有用,我还为像我这样的懒人提供了 JavaConfig 复制粘贴替代方案:)
@Bean
public StepScope stepScope() {
final StepScope stepScope = new StepScope();
stepScope.setAutoProxy(true);
return stepScope;
}
鉴于您正在使用 @RunWith(SpringRunner.class)
,在 class 上方声明 @TestExecutionListeners({..., StepScopeTestExecutionListener.class})
将为您设置作用域。
与 jobScope 的 @TestExecutionListeners({..., JobScopeTestExecutionListener.class})
相同。
别忘了配置
spring.main.allow-bean-definition-overriding=true
你可以通过两种方式解决:
一个。
在application.properties
中使用spring.main.allow-bean-definition-overriding=true
B.
解决 spring 启动批处理范围问题并避免使用 spring.main.allow-bean-definition-overriding=true
:
解决方案:
禁用 StepScope autoProxy
。要执行该操作,请使用以下代码片段作为参考:
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="false"/>
</bean>
现在执行以下 Spring bean 配置:
A.)
通过XML-基于配置配置步骤范围:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="step">
<aop:scoped-proxy/>
</bean>
B.)
通过Java-基于配置配置步骤范围:
@StepScope
@Component(value = "userPreferences")
public class UserPreference {}
只需在测试中添加 @SpringBatchTest
class 即可。
我正在执行独立 spring 批处理作业。这在 JUNIT
中工作得很好@RunWith(SpringJUnit4ClassRunner.class)
//@SpringApplicationConfiguration(classes = KPBootApplication.class)
@ContextConfiguration(locations={"classpath:kp-sb.xml"})
public class KPBootApplicationTests {
private final static Logger LOG=LoggerFactory.getLogger(KPBootApplicationTests.class);
@Autowired
ApplicationContext context;
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Test
public void testJob() {
final JobParameters jobParameters = new JobParametersBuilder()
.toJobParameters();
JobExecution execution;
try {
execution = jobLauncher.run(job, jobParameters);
final ExitStatus status = execution.getExitStatus();
if (ExitStatus.COMPLETED.getExitCode().equals(status.getExitCode())) {
LOG.info("Job completed Yeaaaaaaaa!!!!!");
} else {
final List<Throwable> exceptions = execution
.getAllFailureExceptions();
for (final Throwable throwable : exceptions) {
LOG.error(throwable.getMessage(), throwable);
}
}
} catch (JobExecutionAlreadyRunningException e) {
LOG.error(e.getMessage(), e);
} catch (JobRestartException e) {
LOG.error(e.getMessage(), e);
} catch (JobInstanceAlreadyCompleteException e) {
LOG.error(e.getMessage(), e);
} catch (JobParametersInvalidException e) {
LOG.error(e.getMessage(), e);
}
}
}
和配置文件
<!-- Below code till Job Repo is commented out during spring-boot -->
<context:property-placeholder
properties-ref="kpProps" />
<util:properties id="kpProps">
<prop key="app.file.path">
D:/temp/kp1/all
</prop>
<prop key="app.max_thread_num">
10
</prop>
</util:properties>
<!--
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="batchTransactionManager" />
</bean>
-->
<bean id="batchTransactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager">
<property name="rollbackOnCommitFailure" value="false" />
</bean>
<bean id="multiResourcePartitionerReq"
class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">
<property name="resources" value="file:${app.file.path}/kp_http_request*" />
</bean>
<bean id="multiResourcePartitionerRes"
class="org.springframework.batch.core.partition.support.MultiResourcePartitioner">
<property name="resources" value="file:${app.file.path}/kp_http_response*" />
</bean>
<bean id="kpPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
destroy-method="destroy">
<property name="maxPoolSize" value="${app.max_thread_num}" />
</bean>
<bean id="idIncrementer1"
class="org.springframework.batch.core.launch.support.RunIdIncrementer" />
<batch:job id="kpGenJob" incrementer="idIncrementer1"
job-repository="jobRepository">
<batch:split id="splitStep" next="step4">
<batch:flow>
<batch:step id="step2">
<batch:partition partitioner="multiResourcePartitionerReq"
step="step2.slave">
<batch:handler task-executor="kpPool" />
</batch:partition>
</batch:step>
</batch:flow>
<batch:flow>
<batch:step id="step3">
<batch:partition partitioner="multiResourcePartitionerRes"
step="step3.slave">
<batch:handler task-executor="kpPool" />
</batch:partition>
</batch:step>
</batch:flow>
</batch:split>
<batch:step id="step4">
<batch:tasklet transaction-manager="batchTransactionManager">
<ref bean="kptasklet" />
</batch:tasklet>
</batch:step>
</batch:job>
<batch:step id="step2.slave">
<batch:tasklet transaction-manager="batchTransactionManager">
<batch:chunk reader="reqItemReader" writer="cvsFileItemWriter"
commit-interval="10000" />
</batch:tasklet>
</batch:step>
<batch:step id="step3.slave">
<batch:tasklet transaction-manager="batchTransactionManager">
<batch:chunk reader="resItemReader" writer="cvsFileItemWriter"
commit-interval="10000" />
</batch:tasklet>
</batch:step>
<bean id="reqItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="#{stepExecutionContext['fileName']}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<!-- split it -->
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="includedFields" value="5,6,8,10,11"></property>
<property name="names"
value="f1,f2,f3,f4,f5" />
<property name="strict" value="false" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="tblHttpData" />
</bean>
</property>
</bean>
</property>
<property name="linesToSkip" value="1"></property>
</bean>
<bean id="tblHttpData" class="com.kp.batch.batch.job.domain.TblHttpData"
scope="prototype" />
<bean id="resItemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="#{stepExecutionContext['fileName']}" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<!-- split it -->
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="includedFields" value="3,4"></property>
<property name="names" value="f1,f2" />
<property name="strict" value="false" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="tblHttpData" />
<property name="strict" value="true" />
</bean>
</property>
</bean>
</property>
<property name="linesToSkip" value="1"></property>
</bean>
<bean id="kptasklet" class="com.kp.batch.batch.job.step.KPTasklet" />
<bean id="cvsFileItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"
scope="step">
<!-- write to this csv file -->
<property name="resource" value="#{stepExecutionContext['fileName']}.tmp" />
<property name="shouldDeleteIfExists" value="true" />
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value="," />
<property name="fieldExtractor">
<bean
class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names"
value="server,refferer,application,baseApplication,httpSize" />
</bean>
</property>
</bean>
</property>
</bean>
主要Class
@SpringBootApplication
@EnableBatchProcessing
@ImportResource(value={"classpath:spring-context.xml"})
public class KPBootApplication {
private final static Logger LOG = LoggerFactory
.getLogger(KPBootApplication.class);
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(
KPBootApplication.class, args);
LOG.info("Application KPBOOT Started");
SayHello hello = (SayHello) ctx.getBean("sayHello");
if (hello != null) {
LOG.debug("hello is not null");
LOG.info("Got message {}", hello.getMessage());
} else {
LOG.debug("hello is null");
}
LOG.info("Done");
}
}
当我注释掉 contextconfiguration 注释并通过启用 @SpringApplicationConfiguration 启用 spring-boot(注意:我已经使用注释 @EnableBatchProcessing 启用了 spring 批处理)并注释掉自动创建的 bean通过 spring-boot 为 spring 批处理,例如 JobRepo 我收到以下错误消息,说 step scope
没有上下文持有者java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:94)
at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:212)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:200)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runReflectiveCall(SpringJUnit4ClassRunner.java:252)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:254)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:217)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=15=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2': Cannot resolve reference to bean 'step2.slave' while setting bean property 'step'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2.slave': Cannot resolve reference to bean 'reqItemReader' while setting bean property 'itemReader'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1469)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:743)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:98)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68)
at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86)
... 25 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'step2.slave': Cannot resolve reference to bean 'reqItemReader' while setting bean property 'itemReader'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1469)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 42 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reqItemReader': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:352)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 52 common frames omitted
Caused by: java.lang.IllegalStateException: No context holder available for step scope
at org.springframework.batch.core.scope.StepScope.getContext(StepScope.java:160)
at org.springframework.batch.core.scope.StepScope.get(StepScope.java:99)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
... 54 common frames omitted
不确定为什么 spring 启动会抛出错误
注意:spring-引导版本 1.2.1
这可能是一个错误(我们仍在调查中),但我们确实有解决办法。原因是当使用 @EnableBatchProcessing
时,自动配置的 StepScope
假定 java 配置,因此不代理步骤作用域的 bean,导致它们创建得太早。解决方法是使用以下配置在 XML 配置中手动配置 StepScope
:
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="true"/>
</bean>
Michael 的评论对我有用,我还为像我这样的懒人提供了 JavaConfig 复制粘贴替代方案:)
@Bean
public StepScope stepScope() {
final StepScope stepScope = new StepScope();
stepScope.setAutoProxy(true);
return stepScope;
}
鉴于您正在使用 @RunWith(SpringRunner.class)
,在 class 上方声明 @TestExecutionListeners({..., StepScopeTestExecutionListener.class})
将为您设置作用域。
与 jobScope 的 @TestExecutionListeners({..., JobScopeTestExecutionListener.class})
相同。
别忘了配置
spring.main.allow-bean-definition-overriding=true
你可以通过两种方式解决:
一个。
在application.properties
中使用spring.main.allow-bean-definition-overriding=true
B.
解决 spring 启动批处理范围问题并避免使用 spring.main.allow-bean-definition-overriding=true
:
解决方案:
禁用 StepScope autoProxy
。要执行该操作,请使用以下代码片段作为参考:
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="false"/>
</bean>
现在执行以下 Spring bean 配置:
A.)
通过XML-基于配置配置步骤范围:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="step">
<aop:scoped-proxy/>
</bean>
B.)
通过Java-基于配置配置步骤范围:
@StepScope
@Component(value = "userPreferences")
public class UserPreference {}
只需在测试中添加 @SpringBatchTest
class 即可。