简体   繁体   English

Spring Boot + Spring Batch + @StepScope =没有Proxied Beans?

[英]Spring Boot + Spring Batch + @StepScope = No Proxied Beans?

I am currently setting up a file import service for an application, which would allow a user to upload a csv file via REST. 我目前正在为应用程序设置文件导入服务,这将允许用户通过REST上传csv文件。 The import of the job will be done via Spring Batch as these jobs may be long running, based on future processing requirements and checks. 作业的导入将通过Spring Batch完成,因为这些作业可能会根据未来的处理要求和检查长时间运行。

According to me the below setup is correct for Spring Boot and Spring Batch, but the code won't compile. 根据我的说法,以下设置对于Spring Boot和Spring Batch是正确的,但代码将无法编译。

The main BatchConfiguration file: 主BatchConfiguration文件:

@Configuration
@EnableBatchProcessing
public class PatientBatchConfiguration {

    @Autowired
    private JobBuilderFactory jobBuilders;

    @Autowired
    private StepBuilderFactory stepBuilders;

    @Autowired
    private PatientFieldSetMapper fieldSetMapper;

    @Autowired
    private PatientItemWriter writer;

    @Bean
    public Job importPatientsFromUpload(){
        return jobBuilders.get("importPatientsFromUpload")
                .start(step())
                .build();
    }

    @Bean
    public Step step(){
        return stepBuilders.get("step")
                .<Patient,Patient>chunk(1)
                .reader(reader(null))
                .writer(writer)
                .build();
    }

    @Bean
    @StepScope
    public ItemReader<Patient> reader(@Value("#{jobParameters['fileName']}") String filePath) {
        FlatFileItemReader<Patient> itemReader = new FlatFileItemReader<Patient>();
        itemReader.setLineMapper(lineMapper());
        itemReader.setResource(new FileSystemResource(filePath));
        return itemReader;
    }

    private LineMapper<Patient> lineMapper() {
        DefaultLineMapper<Patient> lineMapper = new DefaultLineMapper<Patient>();
        DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
        lineTokenizer.setNames(new String[]{"name","surname","idNumber","dob", "email", "cell"});
        lineMapper.setLineTokenizer(lineTokenizer);
        lineMapper.setFieldSetMapper(fieldSetMapper);
        return lineMapper;
    }

}

FieldSetMapper code: FieldSetMapper代码:

@Component
public class PatientFieldSetMapper implements FieldSetMapper<Patient> {

    @Override
    public Patient mapFieldSet(FieldSet fieldSet) throws BindException {

        if(fieldSet == null){
            return null;
        }

        Patient patient = new Patient();
        patient.setName(fieldSet.readString("name"));
        patient.setSurname(fieldSet.readString("surname"));
        patient.setIdNo(fieldSet.readString("idNumber"));
        patient.setDob(0L);
        patient.setEmail(fieldSet.readString("email"));
        patient.setCell(fieldSet.readString("cell"));

        return patient;
    }
}

PatientItemWriter code: PatientItemWriter代码:

@Component
public class PatientItemWriter implements ItemWriter<Patient> {

    @Autowired
    PatientRepository patientRepository;

    @Override
    public void write(List<? extends Patient> list) throws Exception {

        for(Patient patient: list) {
            patientRepository.save(patient);
        }
    }
}

Stacktrace: 堆栈跟踪:

Caused by: java.lang.IllegalArgumentException: Path must not be null
    at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.core.io.FileSystemResource.<init>(FileSystemResource.java:75) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at com.example.batch.patient.PatientBatchConfiguration.reader(PatientBatchConfiguration.java:59) ~[classes/:na]
    at com.example.batch.patient.PatientBatchConfiguration.step(PatientBatchConfiguration.java:49) ~[classes/:na]

And finally the application.properties file 最后是application.properties文件

spring.batch.job.enabled=false

The reason spring.batch.job.enabled=false is located in the properties file, is that the job importPatientsFromUpload will be called from a controller after the user has upload a file, and without it, jobs will run at startup. spring.batch.job.enabled = false位于属性文件中的原因是,在用户上传文件后,将从控制器调用作业importPatientsFromUpload,如果没有它,则作业将在启动时运行。

The problem I am having is that the FileSystemResource is failing to be created because the path can't be null. 我遇到的问题是FileSystemResource无法创建,因为路径不能为null。 However I understood that as soon as one annotates a method with @StepScope, a proxy bean will be created, which would allow me to use the jobParameter passed through at runtime to create a new file system resource. 但是我知道只要用@StepScope注释一个方法,就会创建一个代理bean,这将允许我使用在运行时传递的jobParameter来创建一个新的文件系统资源。 I have seen various examples online of using jobParameters this way, but for some reason, the bean seems not to be created correctly. 我已经在网上看到了各种使用jobParameters的例子,但由于某些原因,bean似乎没有正确创建。 I am not sure whether this is related to the fact that I am using Spring Boot, or some other error. 我不确定这是否与我使用Spring Boot或其他错误有关。

Any help would be appreciated. 任何帮助,将不胜感激。 Thanks in advance. 提前致谢。

UPDATE in Reply to Gaël 更新Gaël的更新

PatientItemReader code: PatientItemReader代码:

@Component
@StepScope
public class PatientItemReader implements ItemReader<Patient> {

    @Autowired
    private PatientFieldSetMapper fieldSetMapper;

    private FlatFileItemReader<Patient> itemReader;

    @Override
    public Patient read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        return itemReader.read();
    }

    public PatientItemReader(@Value("#{jobParameters[filePath]}") String filePath) {
            itemReader = new FlatFileItemReader<Patient>();
            DefaultLineMapper<Patient> lineMapper = new DefaultLineMapper<Patient>();
            DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
            lineTokenizer.setNames(new String[]{"name","surname","idNumber","dob", "email", "cell"});
            lineMapper.setLineTokenizer(lineTokenizer);
            lineMapper.setFieldSetMapper(fieldSetMapper);
            itemReader.setLineMapper(lineMapper);
            itemReader.setResource(new FileSystemResource(filePath));
    }
}

I guess the problem is you are creating the reader outside of Spring and passing it a null value when doing this: 我想问题是你是在Spring之外创建读者并在执行此操作时将其传递给null

@Bean
public Step step(){
    ...
    .reader(reader(null))
    ...
}

You should do something like this in order to user Spring capabilities like the late binding: 你应该做这样的事情,以便使用像后期绑定这样的Spring功能:

@Autowired
private ItemReader<Patient> reader;

@Bean
public Step step(){
    ...
    .reader(reader)
    ...
}

As you did for your writer. 正如你为作家所做的那样。

when you using @Bean, it's meaning you want Spring container to manage your instance. 当你使用@Bean时,这意味着你希望Spring容器来管理你的实例。 So, in this step: 所以,在这一步:

@Bean
public Step step() {...}

When you want to get a reader instance to build an instance of step, you can't use like reader(null) . 如果要获取读者实例来构建步骤实例,则不能使用类似reader(null) You should set the reader as a paramater of step, then Spring container will inject the reader instance. 您应该将阅读器设置为步骤的参数,然后Spring容器将注入阅读器实例。 This is the rigth code: 这是严格的代码:

@Bean
public Step step(ItemReader<Patient> reader){
    return stepBuilders.get("step")
            .<Patient,Patient>chunk(1)
            .reader(reader)
            .writer(writer)
            .build();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM