美文网首页
spring batch @jobscope: No conte

spring batch @jobscope: No conte

作者: Double_winter | 来源:发表于2019-08-18 14:32 被阅读0次

    问题:  当我们使用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 里面怎么玩的。欢迎多交流交流!

    相关文章

      网友评论

          本文标题:spring batch @jobscope: No conte

          本文链接:https://www.haomeiwen.com/subject/ihersctx.html