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 里面怎么玩的。欢迎多交流交流!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容