spring batch @jobscope: No context holder available

問題:? 當我們使用spring batch 時候,我們使用@jobscope 來參數專遞,或者使用@stepscope 來使用step execution 上下文, 我們發現 比較奇怪的錯誤就會出現:?Caused by: java.lang.IllegalStateException:?No context holder available for job scope.? 我們不禁會問,@jobscope 用錯了嗎??@jobscope 和 @stepscope 到底再背后做了什么? 為什么會在使用多線程的時候,就會出現這種錯誤呢?

解決思路:?

1. @jobscope, @stepscope 是什么區別?

2. 加上@jobscope, @stepscope,Bean是如何做延遲綁定的?

3. flow 的并行執行,為什么會出現?Caused by: java.lang.IllegalStateException:?No context holder available for job scope?

好了,那我們來看看是什么問題。

第一: jobscope, stepscope 區別請看spring batch 的官網,簡單理解就是job scope context 和 step scope context?

第二: 延遲加載,如何做延遲加載?

其實很簡單,咱們去看看spring batch 源碼。

private T createLazyProxy(AtomicReference reference, Class type) {

ProxyFactory factory =new ProxyFactory();

? factory.setTargetSource(new ReferenceTargetSource<>(reference));

? factory.addAdvice(new PassthruAdvice());

? factory.setInterfaces(new Class[] { type });

? @SuppressWarnings("unchecked")

T proxy = (T) factory.getProxy();

? return proxy;

}

咱們去看看set 的 target source 是什么??ReferenceTargetSource extend?AbstractLazyCreationTargetSource, 當使用這個對象的時候,調用createObject, 可以獲取相應的對象。 從?AbstractLazyCreationTargetSource注解,我們可以了解到。

/**??

Useful when you need to pass a reference to some dependency to an object

* but you don't actually want the dependency to be created until it is first used.

* A typical scenario for this is a connection to a remote resource.

**/

private class ReferenceTargetSource?extends AbstractLazyCreationTargetSource {

private AtomicReferencereference;

? public ReferenceTargetSource(AtomicReference reference) {

this.reference = reference;

? }

@Override

? protected ObjectcreateObject()throws Exception {

initialize();

? ? ? return reference.get();

? }

}

這里面為什么使用AtomicReferencereference, 請大家看看這個類的使用就明白了。但是我確實覺得延遲加載 有哪里優勢,因為我看到每個bean 實際上也是通過代理方式創建了,哦,明白了,這個代理只是將此類的xxx.class 傳入代理類, 那個bean根本就沒有初始化。等調用的時候,可以通過new instance 將引用注入進去。?

很具體的,大家可以搜搜延遲加載怎么實現,應該怎么寫延遲加載的應用。應該不難。 好了,咱們繼續分析問題在哪里? 我們只要記得 所有bean,加上?@jobscope, @stepscope? 之后,都不回加載的,而是等到使用這個bean 的時候,才會去加載,去執行bean 里面的內容。?

第三: 為什么使用flow 之后,想并行執行 step的時候,就會出現標題中的錯誤呢? 下面是簡單的flow 配置:

@Bean(name ="singleInterchangeSettlementStatisticJob")

public JobsetlStatJob(

JobBuilderFactory jobBuilderFactory,

? ? ? ? ? ? Flow splitFlow,

? ? ? ? ? ? Step ichgStatTaskletMergedStep,

? ? ? ? ? ? Step ichgSetlStatClearStep) {

return jobBuilderFactory.get(JobEnum.SNGL_ICHG_SETL_STAT.getJobName())

.incrementer(new RunIdIncrementer())

.start(splitFlow)

.next(ichgStatTaskletMergedStep)

.next(ichgSetlStatClearStep)

?.build()

.build();

? ? }

/**

* parallel step

*

? ? * @param financeStatFlow

? ? * @param nonFinanceStatFlow

? ? * @return Flow

*/

? ? @Bean

? ? public FlowsplitFlow(Flow financeStatFlow,

? ? ? ? ? ? ? ? ? ? ? ? ? Flow nonFinanceStatFlow) {

return new FlowBuilder("ichgStatFlow")

.split(taskExecutor())

.add(financeStatFlow, nonFinanceStatFlow)

.build();

? ? }

/**

* finance Stat Flow

*

? ? * @param snglFinclSetlStatMasterStep

? ? * @return Flow

*/

? ? @JobScope

@Bean

? ? public FlowfinanceStatFlow(Step snglFinclSetlStatMasterStep) {

System.out.println("hhhhhhhhhnonFinanceStatFlowwwwww");

? ? ? ? System.out.println(Thread.currentThread().getName());

? ? ? ? return new FlowBuilder("financeStatFlow")

.start(snglFinclSetlStatMasterStep)

.build();

? ? }

/**

* non Finance Stat Flow

*

? ? * @param snglNonFinclSetlStatMasterStep

? ? * @return Flow

*/

? ? @JobScope

@Bean

? ? public FlownonFinanceStatFlow(Step snglNonFinclSetlStatMasterStep) {

System.out.println("hhhhhhhhhnonFinanceStatFlow");

? ? ? ? System.out.println(Thread.currentThread().getName());

? ? ? ? return new FlowBuilder("nonFinanceStatFlow")

.start(snglNonFinclSetlStatMasterStep)

.build();

? ? }

上面的配置,啟動不會有問題,但是當調用job 執行的時候,就會出現標題中的問題, 為什么呢?? 另外上面的代碼中,jobscope 加錯了位置也會出現標題中的問題,為什么呢??

第一:? 我們通過錯誤技術棧,我們知道,報錯是 getContext, 出現null 的異常。

第二: 我們掉進去 進入getContext,我們知道 里面的實現是使用 thread local

第三: 正常情況下不會出現標題上的問題,是因為JobSynchronizationManager.register(), 的調用。

那么我們如何避免上面的問題呢??

首先: 使用延遲加載的方式,讓bean(方法上有jobscope, stepscope) 不去加載。所以@jobscope, stepscope 一定要注意位置

其次: 并行執行step時候,為flow加一個 executor,這個executor就是為了將該線程注入jobExecution的。?

比如,如果將?@jobscope, 放到splitFlow 方法上,啟動就會報錯,因為?singleInterchangeSettlementStatisticJob 這個bean 加載?splitFlow bean 的時候,會去取jobExecution 上下文,這個時候是為null, 會報錯。

那么這個?executor 怎么寫呢? 這個就很簡單,?

@Bean

public SimpleAsyncTaskExecutortaskExecutor() {

return new SimpleAsyncTaskExecutor() {

@Override

? ? ? ? protected void doExecute(Runnable task) {

//gets the jobExecution of the configuration thread

? ? ? ? ? ? JobExecution jobExecution = JobSynchronizationManager.getContext().getJobExecution();

? ? ? ? ? ? super.doExecute(new Runnable() {

@Override

? ? ? ? ? ? ? ? public void run() {

JobSynchronizationManager.register(jobExecution);

? ? ? ? ? ? ? ? ? ? try {

task.run();

? ? ? ? ? ? ? ? ? ? }finally {

JobSynchronizationManager.release();

? ? ? ? ? ? ? ? ? ? }

}

});

? ? ? ? }

};

}


有人可能會問?financeStatFlow 和?nonFinanceStatFlow 為什么不會出現異常呢? 它們也加了jobscope 注解,這個是因為.split(taskExecutor()), 這個使得?financeStatFlow 和?nonFinanceStatFlow不會加載的,所以不會創建這個bean, 所以沒有上下文的問題。 等需要創建這個bean 的時候,上面的方法已經初始化JobExecution 上線文了。 所以不會有問題。

這個是spring batch的一個bug,網上有很多都爭議關于這個話題,spring batch 下面個版本說有解決,我們拭目以待把,大家有現在有遇到這個問題的,歡迎留言,一起解決。我這邊是可以解決的,然后debug 大概知道spring batch 里面怎么玩的。歡迎多交流交流!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容