Spring Batch StepScope与代理问题:如何为步骤执行存储状态?
问题:Spring Batch中@StepScope代理异常与步骤状态存储
我想为处理器存储可访问的步骤相关状态,于是创建了StateService类及对应的Bean,配置代码如下:
@Slf4j @Configuration @EnableScheduling @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class MyConfiguration { // Job, reader and writer beans @Bean("myStep") Step myStep(@Qualifier("myReader") ItemReader<InputEntity> reader, @Qualifier("myProcessor") ItemProcessor<InputEntity, OutputEntity> processor, @Qualifier("myWriter") ItemWriter<OutputEntity> writer) { return stepBuilderFactory .get("myStep") .<InputEntity, OutputEntity> chunk(100) .reader(reader) .processor(processor) .writer(writer) .build(); } @StepScope @Bean("myProcessor") public MyProcessor processingStep(StateService s) { var processor = new MyProcessor(); processor.setStateService(s); return processor; } @Scope(value = "step", proxyMode = ScopedProxyMode.NO) @Bean public StateService stateService() { return new StateService(); } }
我的目标是为每个新的步骤执行创建一个独立的StateService实例(该类目前为空,且无@Component注解),但运行时抛出了Spring代理相关异常:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'MyProcessor' is expected to be of type 'very.long.package.name.steps.MyProcessor' but was actually of type 'com.sun.proxy.$Proxy265'
我尝试了以下方案,但都没能解决问题:
- 为
stateServiceBean设置所有可能的代理模式; - 通过
@Autowired直接将StateService注入MyProcessor; - 为配置类添加
@EnableBatchProcessing注解; - 直接调用
stateService()方法:processor.setStateService(stateService()); - 修改Step Bean的方法签名,直接注入
MyProcessor而非ItemProcessor<InputEntity, OutputEntity>
我对@StepScope的概念存在哪些误解?如何正确为特定步骤执行存储状态?
解决方案
1. 代理异常的根本原因
你遇到的类型不匹配问题核心原因有两点:
@StepScope默认使用JDK动态代理,而JDK代理仅支持接口。如果MyProcessor是普通类(未实现接口),Spring尝试生成的代理类无法被强转为MyProcessor类型。- 你给
StateService设置了proxyMode = ScopedProxyMode.NO,禁用了代理——但步骤作用域的Bean是延迟初始化的,必须通过代理在步骤执行时获取实例,禁用代理会破坏依赖注入逻辑,进而影响MyProcessor的代理生成。
2. 纠正@StepScope的使用误解
@StepScope是Spring Batch的自定义作用域,用于每个步骤执行时创建新的Bean实例,而非单例。它必须配合@EnableBatchProcessing使用,否则步骤作用域不会生效。- 步骤作用域的Bean依赖其他步骤作用域Bean时,不能禁用代理,必须保留合适的代理模式以支持延迟初始化。
3. 正确实现步骤级状态存储的方案
方案一:让MyProcessor实现ItemProcessor接口(推荐)
如果MyProcessor还未实现ItemProcessor接口,先完成接口实现,同时调整StateService的注解配置:
@Slf4j @Configuration @EnableScheduling @EnableBatchProcessing // 必须添加,启用Spring Batch上下文 public class MyConfiguration { private final StepBuilderFactory stepBuilderFactory; // 构造注入StepBuilderFactory(@EnableBatchProcessing自动注册该Bean) public MyConfiguration(StepBuilderFactory stepBuilderFactory) { this.stepBuilderFactory = stepBuilderFactory; } @Bean("myStep") Step myStep(@Qualifier("myReader") ItemReader<InputEntity> reader, @Qualifier("myProcessor") ItemProcessor<InputEntity, OutputEntity> processor, @Qualifier("myWriter") ItemWriter<OutputEntity> writer) { return stepBuilderFactory .get("myStep") .<InputEntity, OutputEntity>chunk(100) .reader(reader) .processor(processor) .writer(writer) .build(); } @StepScope @Bean("myProcessor") public ItemProcessor<InputEntity, OutputEntity> processingStep(StateService stateService) { MyProcessor processor = new MyProcessor(); processor.setStateService(stateService); return processor; } @StepScope // 直接用@StepScope,默认代理模式适配环境 @Bean public StateService stateService() { return new StateService(); } } // MyProcessor实现ItemProcessor接口 public class MyProcessor implements ItemProcessor<InputEntity, OutputEntity> { private StateService stateService; @Override public OutputEntity process(InputEntity item) throws Exception { // 使用stateService存储步骤执行状态 return null; } public void setStateService(StateService stateService) { this.stateService = stateService; } }
方案二:为MyProcessor强制使用CGLIB代理
如果MyProcessor无法实现接口,需要强制Spring使用CGLIB代理生成类实例:
@StepScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Bean("myProcessor") public MyProcessor processingStep(StateService stateService) { var processor = new MyProcessor(); processor.setStateService(stateService); return processor; } @StepScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Bean public StateService stateService() { return new StateService(); }
Spring Boot项目默认已包含CGLIB依赖,无需额外引入。
方案三:直接在MyProcessor中维护状态(更简洁)
如果StateService仅用于存储当前步骤执行的状态,可省略该类,直接在@StepScope修饰的MyProcessor中定义状态变量——因为@StepScope的MyProcessor本身就是每个步骤执行一个独立实例:
@StepScope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Bean("myProcessor") public MyProcessor processingStep() { return new MyProcessor(); } // MyProcessor类中直接维护步骤专属状态 public class MyProcessor implements ItemProcessor<InputEntity, OutputEntity> { // 该变量为当前步骤执行的专属状态 private int processedCount = 0; @Override public OutputEntity process(InputEntity item) throws Exception { processedCount++; // 业务逻辑处理 return null; } }
关键总结
@StepScope必须配合@EnableBatchProcessing使用,否则步骤作用域不生效。- 当Bean为普通类而非接口实现类时,需指定
proxyMode = ScopedProxyMode.TARGET_CLASS启用CGLIB代理,避免类型转换异常。 - 步骤作用域的Bean之间的依赖,不能禁用代理(
ScopedProxyMode.NO),否则无法完成延迟初始化。
内容的提问来源于stack exchange,提问作者Arli Chokoev
相关产品推荐
相关产品推荐

