从 StaxWriterCallback 访问执行上下文

Access execution context from StaxWriterCallback

我正在执行一项作业,该作业将从数据库加载数据并将结果写入 xml 文件。 xml 文件将有一个 header,它基于存储在执行上下文中的某些属性。因此,我想访问执行上下文。我在这份工作的其他 beans 中完成了此操作,但我无法弄清楚如何从我的 StaxWriterCallback 中访问执行上下文。

在工作中,我的作家目前实施为:

@Bean(name = "writer")
@StepScope
public StaxEventItemWriter<Data> writer(@Value("#{jobParameters['path']}") String path) {
        
    StaxEventItemWriter<Data> itemWriter = new StaxEventItemWriter<Data>();
    itemWriter.setVersion("1.0");
    itemWriter.setEncoding("UTF-8");
    itemWriter.setStandalone(false);
        
    itemWriter.setHeaderCallback(headerCallback());
    itemWriter.setFooterCallback(new CustomXMLFooterCallback());
        
    itemWriter.setMarshaller(new XStreamMarshaller());
                
    FileSystemResource file = new FileSystemResource(path + "test.xml");
    itemWriter.setResource(file);
        
    return itemWriter;
}

header回调实现为:

@Component
public class CustomXMLHeaderCallback implements StaxWriterCallback {
    
    private StepExecution stepExecution;

    @BeforeStep
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }

    @Override
    public void write(XMLEventWriter writer) throws IOException {
        
        String currentFile = stepExecution.getExecutionContext().getString("currentFile");

        try {

            XMLEventFactory eventFactory = XMLEventFactory.newInstance();
            XMLEvent end = eventFactory.createDTD("\n");
            XMLEvent tab = eventFactory.createDTD("\t");

            writer.add(eventFactory.createStartElement("", "", "file"));
            writer.add(eventFactory.createCharacters(currentFile.substring(19, 31)));
            writer.add(eventFactory.createEndElement("", "", "file"));
            writer.add(end);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

header 回调被添加到批处理作业中:

@Bean
@StepScope
public CustomXMLHeaderCallback headerCallback() {
    return new CustomXMLHeaderCallback();
}

不幸的是,我收到以下错误:

java.lang.NullPointerException: Cannot invoke "org.springframework.batch.core.StepExecution.getExecutionContext()" because "this.stepExecution" is null
    at com.test.xml.callback.CustomXMLHeaderCallback.write(CustomXMLHeaderCallback.java:39) ~[classes/:na]
    at com.test.xml.callback.CustomXMLHeaderCallback$$FastClassBySpringCGLIB$$e0795ecb.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.3.jar:5.3.3]
    at com.test.xml.callback.CustomXMLHeaderCallback$$EnhancerBySpringCGLIB$$e9083311.write(<generated>) ~[classes/:na]
    at org.springframework.batch.item.xml.StaxEventItemWriter.open(StaxEventItemWriter.java:447) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1]
    at org.springframework.batch.item.xml.StaxEventItemWriter$$FastClassBySpringCGLIB$$d105dd1.invoke(<generated>) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.batch.item.xml.StaxEventItemWriter$$EnhancerBySpringCGLIB$9f2f97.open(<generated>) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1]
    at org.springframework.batch.item.support.CompositeItemStream.open(CompositeItemStream.java:104) ~[spring-batch-infrastructure-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.step.tasklet.TaskletStep.open(TaskletStep.java:311) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:205) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:411) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:136) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:320) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:147) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) ~[spring-core-5.3.3.jar:5.3.3]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:140) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:128) ~[spring-batch-core-4.3.1.jar:4.3.1]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.3.jar:5.3.3]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.3.jar:5.3.3]
    at com.sun.proxy.$Proxy62.run(Unknown Source) ~[na:na]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.execute(JobLauncherApplicationRunner.java:199) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.executeLocalJobs(JobLauncherApplicationRunner.java:173) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.launchJobFromProperties(JobLauncherApplicationRunner.java:160) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:155) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:150) ~[spring-boot-autoconfigure-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:795) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:785) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:333) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311) ~[spring-boot-2.4.2.jar:2.4.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300) ~[spring-boot-2.4.2.jar:2.4.2]
    at com.test.main(MainOutboundDatins.java:18) ~[classes/:na]

如果您希望 beforeStep 方法在您的 CustomXMLHeaderCallback 中的步骤之前被调用,您需要使用步骤构建器的 [=14] 之一将该组件注册为步骤中的侦听器=] 方法。这在此处的文档中进行了解释:Intercepting Step Execution.

否则,您可以使用 @Value:

注入步骤执行
@Value("#{StepExecution}")
private StepExecution stepExecution;

第三个选项是从步骤执行上下文中注入所需的属性:

// define your header as follows:
public class CustomXMLHeaderCallback implements StaxWriterCallback {

   private String currentFile;

   public CustomXMLHeaderCallback(String currentFile) {
      this.currentFile = currentFile;
   }

   @Override
   public void write(XMLEventWriter writer) throws IOException {
      // use this.currentFile as needed here
   }
   

}

// decalre your bean as follows:
@Bean
@StepScope
public CustomXMLHeaderCallback headerCallback(@Value("#{stepExecutionContext['currentFile']}") String currentFile) {
    return new CustomXMLHeaderCallback(currentFile);
}

我推荐第三个选项,因为它只注入所需的属性,而不是整个步骤执行对象。