1.StepSopeはスコープです
その前に、IOCコンテナ内のいくつかのBeanのスコープについて説明しましょう。
-
シングルトンシングルトンパターン-グローバルにインスタンスは1つだけです
-
プロトタイププロトタイプパターン-Beanを取得するたびに、新しいインスタンスがあります
-
request – requestは、HTTPリクエストごとに新しいBeanが生成されることを示し、Beanは現在のHTTPリクエスト内でのみ有効です。
-
セッション–セッションスコープは、HTTPリクエストごとに新しいBeanが生成されることを示し、Beanは現在のHTTPセッション内でのみ有効です。
-
globalsession-グローバルセッションスコープは標準のHTTPセッションスコープに似ていますが、ポートレットベースのWebアプリケーションでのみ意味があります
2.StepSopeはカスタムステップです
目的は、システム内に複数のジョブインスタンスが存在する可能性があり、各ジョブインスタンスに対応するパラメーターが異なるため、ジョブインスタンスが開始されるたびに、基になるユニット(RPWまたはステップ)のパラメーターを柔軟に変更または可変できることです。 。。ステップ操作中に取得する必要があるジョブパラメーター/stepContext/ jobContext。したがって、スコープをカスタマイズして、ステップのライフサイクルと一致させる必要があります。
注釈を使用します。次に、stepScopeを@Beanで変更する必要があります
3.使用方法。@Valueはspel式でサポートされています
3.1ほとんどのシナリオはSpel式です。基になるリーダー/プロセス/ライターで@Valueを使用してjobParamter/stepContext/jobContextを取得します
-
ジョブパラメータ:#{jobParameters [xy]}
-
ジョブ実行コンテキスト:#{jobExecutionContext [xy]}
-
ステップ実行コンテキスト:#{stepExecutionContext [xy]}
3.2SpELリファレンス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.可能遇到问题
- 问题: 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.スコープをカスタマイズする
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を注入します
@Component
public 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();
}
}
复制代码
引用されていません
@Service
public 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: 1842102517
Hello World!1842102517=1842102517
Exists jackssybinHaveScopeService; hashCode: 1842102517
Hello World!1842102517=1842102517
======================
Hello World!728236551=728236551
Hello World!728236551=728236551
复制代码
結論は:
- jackssybinScopeが有効になります。
- ctxのBeanは、デフォルトではシングルトンです。
コードの場所:github.com/jackssybin/…
この記事は、複数投稿のブログプラットフォームであるOpenWriteによって公開されています。