Spring Batch中使用@StepScope注解触发ReaderNotOpenException问题的解决方法
我之前也碰到过一模一样的问题,给JdbcCursorItemReader加上@StepScope后直接触发了ReaderNotOpenException,其实核心原因是**@StepScope生成的代理对象没有正确转发ItemStream的生命周期方法**。
问题根源
JdbcCursorItemReader本身实现了ItemStream接口,这个接口定义了open()、update()、close()这些关键的生命周期方法——Spring Batch必须在step执行前调用open()来初始化数据库游标和连接。但当你加上@StepScope后,Spring会生成一个代理对象延迟创建真实的reader实例,如果你的bean返回类型是ItemReader(而非ItemStreamReader),这个代理不会转发ItemStream的方法,导致真实reader的open()方法从未被调用,自然就会抛出"Reader must be open before it can be read"的异常。
修复方案
最简单的修复方式就是把bean的返回类型从ItemReader改成ItemStreamReader,这样Spring的代理会正确处理所有ItemStream的生命周期方法,让Spring Batch能在step启动时正常初始化reader:
@Bean @StepScope public ItemStreamReader<ABC> itemReader1() { return new JdbcCursorItemReaderBuilder<ABC>() .name("itemReader1") .dataSource(this.dataSource) .beanRowMapper(ABC.class) .sql("select * from abc") .build(); }
额外注意事项
如果你需要在reader中使用step范围内的参数(比如从JobParameters里取值),可以通过@Value注入,这也是@StepScope的典型用法,比如:
@Bean @StepScope public ItemStreamReader<ABC> itemReader1(@Value("#{jobParameters['abcId']}") Long abcId) { return new JdbcCursorItemReaderBuilder<ABC>() .name("itemReader1") .dataSource(this.dataSource) .beanRowMapper(ABC.class) .sql("select * from abc where id = :abcId") .parameterValues(Map.of("abcId", abcId)) .build(); }
这样既利用了@StepScope的延迟加载和参数注入能力,又能保证reader被正确初始化,不会再出现未打开的异常。
内容的提问来源于stack exchange,提问作者imsaiful

