Spring Batch 6.1.0-RC1 给 job / step 作用域 bean 加了 @JobParameter。以前要写 @Value("#{jobParameters['input.file']}"),现在可以写成 @JobParameter("input.file"),参数名和字段名相同时连括号里的名字都能省。用 Spring Boot 4.2.0-RC1(BOM 绑定 Batch 6.1.0-RC1)搭了一个 H2 作业对照跑:同一套作业参数下,SpEL 与注解注入的字符串完全一致;参数缺失时两边都是 null;离开 job / step scope 则会直接炸在启动期。
Javadoc 写明 @since 6.1,GitHub issue #5563 也是这一条增量。之前 6.1.0-M2 那篇 谈的是异步 API 迁 core、Avro 受信类门槛;Boot 4.1 把 JobRepository 放进 MongoDB 还停在 Batch 6.0.5。本文只补 RC1 这条注入语法,不重复那些主题。
环境和依赖
实测环境:JDK 21.0.12.1、Spring Boot 4.2.0-RC1、Spring Batch 6.1.0-RC1、H2 2.5.252、内嵌 JDBC JobRepository。Boot 4 把 Batch JDBC 自动配置拆进了 spring-boot-starter-batch-jdbc:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.2.0-RC1</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
dependency:tree 里能直接看到对齐关系:
+- org.springframework.boot:spring-boot-starter-batch-jdbc:jar:4.2.0-RC1:compile
| +- org.springframework.boot:spring-boot-starter-batch:jar:4.2.0-RC1:compile
| | \- org.springframework.boot:spring-boot-batch:jar:4.2.0-RC1:compile
| | \- org.springframework.batch:spring-batch-core:jar:6.1.0-RC1:compile
| | +- org.springframework.batch:spring-batch-infrastructure:jar:6.1.0-RC1:compile
| \- org.springframework.boot:spring-boot-batch-jdbc:jar:4.2.0-RC1:compile
\- com.h2database:h2:jar:2.5.252:runtime
application.properties 开 schema 初始化,关掉 Web:
spring.datasource.url=jdbc:h2:mem:batch;DB_CLOSE_DELAY=-1
spring.batch.jdbc.initialize-schema=always
spring.main.web-application-type=none
编译无警告。Boot 的 Maven 插件默认带 -parameters,方法参数名可被反射读到,后面按参数名省略 @JobParameter 的 value 才能成立。
旧 SpEL 和新注解对照
同一个 @StepScope 的 Tasklet 工厂方法里,四种写法并排:
@Bean
@StepScope
Tasklet greetingTasklet(
@Value("#{jobParameters['input.file']}") String spelInputFile,
@JobParameter("input.file") String annotationInputFile,
@JobParameter String region,
@JobParameter("missing.optional") String missingOptional) {
return (contribution, chunkContext) -> {
log.info("SpEL 注入 input.file = [{}]", spelInputFile);
log.info("@JobParameter(\"input.file\") = [{}]", annotationInputFile);
log.info("@JobParameter 按参数名注入 region = [{}]", region);
log.info("@JobParameter(\"missing.optional\") 缺失时 = [{}]", missingOptional);
boolean same = (spelInputFile == null && annotationInputFile == null)
|| (spelInputFile != null && spelInputFile.equals(annotationInputFile));
log.info("SpEL 与 @JobParameter(\"input.file\") 结果一致 = {}", same);
return RepeatStatus.FINISHED;
};
}
前两行是同一参数的新旧写法。第三行故意不写名字,靠方法参数名 region 去匹配作业参数。第四行故意要一个命令行里没给的键,观察缺失行为。
注解定义在 org.springframework.batch.core.configuration.annotation.JobParameter,目标是字段和方法 / 构造参数,自身还带了 @Autowired。Javadoc 原话:标 @JobParameter 等价于 @Value("#{jobParameters['key']}");参数不存在时注入 null;bean 既不是 job scope 也不是 step scope 时,解析会失败。
跑通一次
命令行参数跟在后面,key=value 会变成 identifying 的作业参数:
mvn spring-boot:run -Dspring-boot.run.arguments="input.file=data/orders.csv region=cn-east run.id=1"
启动横幅和作业日志(时间戳原样):
:: Spring Boot :: (v4.2.0-RC1)
2026-11-03T08:10:57.948+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] c.e.b.Batch61JobParameterApplication : Starting Batch61JobParameterApplication using Java 21.0.12.1 with PID 1417264
2026-11-03T08:10:58.715+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] c.e.b.Batch61JobParameterApplication : Started Batch61JobParameterApplication in 1.02 seconds (process running for 1.19)
2026-11-03T08:10:58.716+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] o.s.b.b.a.JobLauncherApplicationRunner : Running default command line with: [input.file=data/orders.csv, region=cn-east, run.id=1]
2026-11-03T08:10:58.822+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=greetingJob]] launched with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.String, identifying=true},JobParameter{name='region', value=cn-east, type=class java.lang.String, identifying=true},JobParameter{name='input.file', value=data/orders.csv, type=class java.lang.String, identifying=true}}]
2026-11-03T08:10:58.841+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] o.s.batch.core.step.AbstractStep : Executing step: [greetingStep]
2026-11-03T08:10:58.870+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] com.example.batch.GreetingJobConfig : SpEL 注入 input.file = [data/orders.csv]
2026-11-03T08:10:58.870+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] com.example.batch.GreetingJobConfig : @JobParameter("input.file") = [data/orders.csv]
2026-11-03T08:10:58.870+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] com.example.batch.GreetingJobConfig : @JobParameter 按参数名注入 region = [cn-east]
2026-11-03T08:10:58.870+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] com.example.batch.GreetingJobConfig : @JobParameter("missing.optional") 缺失时 = [null]
2026-11-03T08:10:58.870+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] com.example.batch.GreetingJobConfig : SpEL 与 @JobParameter("input.file") 结果一致 = true
2026-11-03T08:10:58.877+08:00 INFO 1417264 --- [batch61-jobparameter-demo] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=greetingJob]] completed with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.String, identifying=true},JobParameter{name='region', value=cn-east, type=class java.lang.String, identifying=true},JobParameter{name='input.file', value=data/orders.csv, type=class java.lang.String, identifying=true}}] and the following status: [COMPLETED] in 50ms
同参两条线读出的都是 data/orders.csv,region 靠参数名对上了 cn-east,没传的 missing.optional 是 null,作业状态 COMPLETED。
注意日志里的 JobParameter{name=...} 是领域模型 org.springframework.batch.core.job.parameters.JobParameter 记录类型的 toString,和注解同名不同物,别混。
离开 scope 会怎样
把同一个工厂方法上的 @StepScope 去掉,再启动:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'greetingTasklet' defined in class path resource [com/example/batch/GreetingJobConfig.class]: Unsatisfied dependency expressed through method 'greetingTasklet' parameter 0: Expression parsing failed
Caused by: org.springframework.beans.factory.BeanExpressionException: Expression parsing failed
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'jobParameters' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?
异常里直接出现 jobParameters。源码里 LateBindingAutowireCandidateResolver(@since 6.1)对标了 @JobParameter 的依赖会建议值:
return String.format("#{jobParameters['%s']}", key);
也就是说注解在装配阶段被翻译成和旧写法同一条 SpEL,再交给原来的 late binding。singleton 上没有 job / step 上下文,SpEL 找不到 jobParameters,于是启动失败。Javadoc 说的「等价于 @Value("#{jobParameters['key']}")」不是修辞,是实现路径。
名字省略时,resolver 用 DependencyDescriptor.getDependencyName();拿不到名字就抛:
No name specified for @JobParameter on ..., and parameter name information is not available via reflection. Specify the name with @JobParameter("name") or compile with the '-parameters' flag.
手写 javac、或关掉 -parameters 时,带点号的键(比如 input.file)本来就该写在注解的 value 里;能省略的主要是「参数名 == 作业参数名」的场景。
迁代码时怎么对齐
@JobParameter 是 Batch 6.1 的语法糖,不改变 JobParameters 的语义,也不替代 JobParametersValidator 或 identifying 参数规则。Boot 4.2.0-RC1 通过继承 DefaultBatchConfiguration 的自动配置会挂上 LateBindingBeanFactoryPostProcessor,不必再手写 @EnableBatchProcessing 也能用这个注解。迁代码时,把 @Value("#{jobParameters['…']}") 换成 @JobParameter("…") 即可;要确认行为,对着同一组参数打一行日志比盯 SpEL 括号更省事。
参考:Spring Batch 6.1.0-RC1 API:`@JobParameter`、issue #5563、Spring Boot 4.2.0 RC1 Release Notes(其中 Batch 版本列为 6.1.0-RC1)。
一起交流
分享你的思考,让讨论更进一步。