1.StepSope 是一种scope

在此之前,先说一下IOC容器中几种bean的作用范畴:

  • singleton单例模式 – 全局有且仅有一个实例
  • prototype原型模式 – 每次获取Bean的时候会有一个新的实例
  • request – request示意该针对每一次HTTP申请都会产生一个新的bean,同时该bean仅在以后HTTP request内无效
  • session – session作用域示意该针对每一次HTTP申请都会产生一个新的bean,同时该bean仅在以后HTTP session内无效
  • globalsession – global session作用域相似于规范的HTTP Session作用域,不过它仅仅在基于portlet的web利用中才有意义

2.StepSope 是一种自定义step

目标是在每一次启动Job实例时底层单位(RPW或者也可是step)的参数能够灵便批改或者达到可变,因为一个零碎里可能有多个job实例,每个job实例对应的参数是不一样的。在step运行期间须要获取的job参数/stepContext/jobContext,因而须要自定义一个作用域,令其与Step的生命周期统一。

应用注解了。那么stepScope润饰的肯定是一个@Bean

3.如何应用。@Value是反对spel表达式的

3.1 大部分场景是Spel 表达式。在底层reader/process/writer 中应用@Value获取jobParamter/stepContext/jobContext
  • job参数:\#{jobParameters[xy]}
  • job运行上下文:#{jobExecutionContext[xy]}
  • step运行上下文:#{stepExecutionContext[xy]}
3.2 SpEL援用bean
  • bean对象:\#{car}
  • bean对象属性:\#{car.brand}
  • bean对象办法:\#{car.toString()}
  • 静态方法属性:\#{T(java.lang.Math).PI}
3.3 零碎属性
  • 零碎变量:systemProperties
  • 环境变量:#{systemEnvironment['HOME']}
3.4 运算符号
  • if-else 运算符(三目运算符 ?:(temary), ?:(Elvis))
  • 比拟运算符(< , > , == , >= , <= , lt , gt , eg , le , ge)
  • 逻辑运算符(and , or , not , |)
  • 正则表达式(#{admin.email matches ‘[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}’})
  • 算术运算符(+,-,*,/,%,^(加号还能够用作字符串连贯))

4.可能遇到问题

  1. 问题: Scope 'step' is not active for the current thread;
Error creating bean with name 'scopedTarget.demo01StepScopeStep': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope

起因:看代码

@Bean    public Job demo01StepScopeJob(){        return jobBuilderFactory.get("demo01StepScopeJob")                .start(demo01StepScopeStepError(null))                .build();    }    /**     ** 这个时候在 Step (demo01StepScopeStep) 中增加注解stepscope。    ** Scope 'step' is not active for the current thread. 这个说的也很明确,step还没有装载初始化实现呢。    ** 所以只有在step激活,即装载胜利之后能力获取@Value 这种状况。咱们能够把taskle 定义成个bean来获取    **/    @Bean    @StepScope     public Step demo01StepScopeStepError(@Value("${demo01.param.name}") String paramName){        return stepBuilderFactory.get("demo01StepScopeStep")                .tasklet(new Tasklet() {                    @Override                    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {                        logger.info("=======demo01StepScopeStep======paramName:{}",paramName);                        return null;                    }                }).build();    }        // 革新如下        @Bean    public Job demo01StepScopeJob(){        return jobBuilderFactory.get("demo01StepScopeJob")                .start(demo01StepScopeStep())                .build();    }    //这个时候step下面的bean能够不要。因为stepScope只须要定义须要获取@Value的    public Step demo01StepScopeStep(){        return stepBuilderFactory.get("demo01StepScopeStep")                .tasklet(demo01StepScopeTasklet(null))                .build();    }    @Bean    @StepScope  //这里的@StepScope 能够有也能够不必。因为@value只是在application.properties中的内容    public Tasklet demo01StepScopeTasklet(@Value("${demo01.param.name}") String paramName){        return new Tasklet() {            @Override            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {                logger.info("=======demo01StepScopeStep======paramName:{}",paramName);                return null;            }        };    }

这外面获取的是配置文件的内容。不是step内的特定内容。所以能够间接应用@Value在参数bean里。也能够间接在configuration内。

若应用jobParam参数内的@Value呢?那必须使@StepScope

    @Bean    @StepScope    public Tasklet demo01StepScopeTasklet(@Value("#{jobParameters[rnd]} ") String rnd){        return new Tasklet() {            @Override            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {                logger.info("=======demo01StepScopeTasklet======rnd:{}",rnd);                return null;            }        };    }

5.StepScope原理

org.springframework.batch.core.scope.StepScope.java

看出stepScope实现了BeanFactoryPostProcessor。引入了bpp即实例化之前做扩大。这里是讲stepScope给注册到了spring容器中

获取bean的时候AbstractBeanFactory#doGetBean。判断bean不是mbd.isSingleton(),不是mbd.isPrototype(),若是自定义的即

mbd.getScope()。

那是如何获取的呢StepScope#get(String, ObjectFactory) ->这外面会获取StepContext内容。

stepContext=getContext() ->这外面其实是间接从ThreadLocal中获取内容。获取stepContext(这外面从有jobParam,stepContext)

那咱们能够看到这个StepSynchronizationManager曾经有获取的。那什么时候注册进去的呢?

AbstractStep#execute中在step的抽象类外面。执行doExecute(stepExecution);之前的办法doExecutionRegistration

这外面将stepExecution增加进ThreadLocal中。这外面能够认为是threadlocal(其实外面是一个栈能够存多个stepExecution!!!,兼容step套step那种的。)

其实这里曾经能解答了。但还有个问题。@Value是如何从stepExecution中获取jobParm/stepContext/jobContext的

额定(通过@value是如何在那个阶段获取值的呢):

springboot启动过程中,有两个比拟重要的过程,如下:
1 扫描,解析容器中的bean注册到beanFactory下来,就像是信息注销一样。
2 实例化、初始化这些扫描到的bean。

@Value的解析就是在第二个阶段。BeanPostProcessor定义了bean初始化前后用户能够对bean进行操作的接口办法,它的一个重要实现类AutowiredAnnotationBeanPostProcessor正如javadoc所说的那样,为bean中的@Autowired@Value注解的注入性能提供反对。上面是调用链

这里先简略介绍一下图上的几个类的作用。

AbstractAutowireCapableBeanFactory: 提供了bean创立,属性填充,主动拆卸,初始胡。反对主动拆卸构造函数,属性按名称和类型拆卸。实现了AutowireCapableBeanFactory接口定义的createBean办法。

AutowiredAnnotationBeanPostProcessor: 拆卸bean中应用注解标注的成员变量,setter办法, 任意的配置办法。比拟典型的是@Autowired注解和@Value注解。

InjectionMetadata: 类的注入元数据,可能是类的办法或属性等,在AutowiredAnnotationBeanPostProcessor类中被应用。

AutowiredFieldElement: 是AutowiredAnnotationBeanPostProcessor的一个公有外部类,继承InjectionMetadata.InjectedElement,形容注解的字段。

StringValueResolver: 一个定义了处理字符串值的接口,只有一个接口办法resolveStringValue,能够用来解决占位符字符串。本文中的次要实现类在PropertySourcesPlaceholderConfigurer#processProperties办法中通过lamda表达式定义的。供ConfigurableBeanFactory类应用。

PropertySourcesPropertyResolver: 属性资源处理器,次要性能是获取PropertySources属性资源中的配置键值对。

PropertyPlaceholderHelper: 一个工具类,用来解决带有占位符的字符串。形如${name}的字符串在该工具类的帮忙下,能够被用户提供的值所代替。代替途经可能通过Properties实例或者PlaceholderResolver(外部定义的接口)。

PropertyPlaceholderConfigurerResolver: 上一行所说的PlaceholderResolver接口的一个实现类,是PropertyPlaceholderConfigurer类的一个公有外部类。实现办法resolvePlaceholder中调用了外部类的resolvePlaceholder办法。

6.自定义一个scope

定义JakcssybinScope

/** * 定义在同一个线程内,屡次获取同一个bean 获取的是同一个。 * 如果不必该注解 获取的是不同的bean */public class JackssybinScope implements Scope {    private final ThreadLocal<Map<String, Object>> threadLoacal = new ThreadLocal<Map<String, Object>>() {        @Override        protected Map<String, Object> initialValue() {            return new HashMap<String, Object>();        }    };    public Object get(String name, ObjectFactory<?> objectFactory) {        Map<String, Object> scope = threadLoacal.get();        Object obj = scope.get(name);        // 不存在则放入ThreadLocal        if (obj == null) {            obj = objectFactory.getObject();            scope.put(name, obj);            System.out.println("Not exists " + name + "; hashCode: " + obj.hashCode());        } else {            System.out.println("Exists " + name + "; hashCode: " + obj.hashCode());        }        return obj;    }    public Object remove(String name) {        Map<String, Object> scope = threadLoacal.get();        return scope.remove(name);    }    public String getConversationId() {        return null;    }    public void registerDestructionCallback(String arg0, Runnable arg1) {    }    public Object resolveContextualObject(String arg0) {        return null;    }}

注入jackssybinScope

@Componentpublic class JackssybinBPP implements BeanFactoryPostProcessor {    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        System.out.println("==============注册JackssybinBPP=========");        beanFactory.registerScope("jackssybinScope", new JackssybinScope());    }}

援用

@Service@Scope("jackssybinScope")public class JackssybinHaveScopeService {    public String getMessage() {        return "Hello World!"+this.hashCode();    }}

未援用

@Servicepublic class JackssybinNoScopeService {    public String getMessage() {        return "Hello World!"+this.hashCode();    }}

测试

@SpringBootTest(classes = {Demo01StepScopeApplication.class})public class JackssybinScopeTest {    @Autowired    ApplicationContext ctx;    @Test    public void jackssybinScopetest2(){        JackssybinHaveScopeService service = ctx.getBean(JackssybinHaveScopeService.class);        System.out.println(service.getMessage()+"="+service.hashCode());        JackssybinHaveScopeService service2= ctx.getBean(JackssybinHaveScopeService.class);        System.out.println(service2.getMessage()+"="+service2.hashCode());        System.out.println("======================");        JackssybinNoScopeService service3 = ctx.getBean(JackssybinNoScopeService.class);        System.out.println(service3.getMessage()+"="+service3.hashCode());        JackssybinNoScopeService service4= ctx.getBean(JackssybinNoScopeService.class);        System.out.println(service4.getMessage()+"="+service4.hashCode());    }}

后果

Not exists jackssybinHaveScopeService; hashCode: 1842102517Hello World!1842102517=1842102517Exists jackssybinHaveScopeService; hashCode: 1842102517Hello World!1842102517=1842102517======================Hello World!728236551=728236551Hello World!728236551=728236551

论断:

  1. jackssybinScope失效了。
  2. ctx中的bean默认是单例的。

代码地位:

https://github.com/jackssybin...