Статьи

Spring Batch — замена конфигурации задания XML на JavaConfig

Недавно я помог клиенту начать работу с реализацией Spring Batch. Команда решила перейти к конфигурации на основе JavaConfig для своих пакетных заданий вместо традиционной конфигурации на основе XML. Поскольку это становится все более распространенным подходом к настройке приложений Java, я почувствовал, что пришло время обновить серию Keyhole Spring Batch, чтобы показать вам, как преобразовать существующую конфигурацию Spring Batch на основе XML в новую конфигурацию на основе аннотаций JavaConfig.

Весна-Batch В этом руководстве будет использовано простое пакетное задание, приведенное во втором из наших учебных пособий Spring Batch ( https://keyholesoftware.com/2012/06/25/getting-started-with-spring-batch-part-two/ ).

Уборка дома

Прежде чем мы сможем начать процесс преобразования, мы должны сделать небольшую уборку дома для проекта.

  1. Обновите свою сборку Java и среду Spring до Java 7, если вы этого еще не сделали.
  2. Добавьте зависимость Spring Boot в Maven pom.xml:
  3. 1
    2
    3
    4
    5
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
        <version>1.2.4.RELEASE</version>
    </dependency>
  4. Измените версию Spring Batch на 3.0.4.RELEASE и версию Spring Framework на 4.1.6.RELEASE
    1
    2
    3
    <properties>                              <spring.framework.version>4.1.6.RELEASE</spring.framework.version>
        <spring.batch.version>3.0.4.RELEASE</spring.batch.version>
    </properties>
  5. Закомментируйте определения заданий в исходном файле конфигурации пакета с именем module-context.xml.
  6. Закомментируйте элементы конфигурации контекста приложения Spring в файле конфигурации с именем launch-context.xml.
  7. Закомментируйте аннотацию @Component для элементов Reader, Processor и Writer. Не закомментируйте аннотацию @Service в классе CurrencyConversionServiceImpl.

Создание конфигурации на основе JavaConfig

Теперь, когда мы удалили или отключили существующую конфигурацию на основе XML, мы можем приступить к созданию конфигурации на основе JavaConfig. Для этого нам нужно создать новый класс с некоторыми аннотациями, которые устанавливают основу для конфигурации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package com.keyhole.example.config;
 
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
 
@Configuration
@EnableBatchProcessing
public class TickerPriceConversionConfig {
 
    @Autowired
    private JobBuilderFactory jobs;
 
    @Autowired
    private StepBuilderFactory steps;
 
}

Аннотация @Configuration позволяет контейнеру Spring знать, что этот класс будет содержать один или несколько аннотированных методов @Bean, которые будут обрабатываться для генерации определений bean-компонентов и запросов на обслуживание во время выполнения.

Аннотация @EnableBatchProcessing предоставляет базовую конфигурацию для создания конфигураций пакетных заданий. Spring Batch использует эту аннотацию для установки заданного по умолчанию JobRepository, JobLauncher, JobRegistry, PlatformTransactionManager, JobBuilderFactory и StepBuilderFactory.

Теперь пришло время добавить наши аннотированные методы @Bean для наших компонентов, которые составляют пакетное задание. Для справки, я включил соответствующую конфигурацию XML для каждого компонента.

Конфигурация ItemReader

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<bean name="tickerReader"
    class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource"
value="http://finance.yahoo.com/d/quotes.csv?s=XOM+IBM+JNJ+MSFT&f=snd1ol1p2" />
    <property name="lineMapper" ref="tickerLineMapper" />
</bean>
  
<bean name="tickerLineMapper"
class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="fieldSetMapper" ref="tickerMapper" />
    <property name="lineTokenizer" ref="tickerLineTokenizer" />
</bean>
  
<bean name="tickerLineTokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer" />
01
02
03
04
05
06
07
08
09
10
@Bean
    public ItemReader<TickerData> reader() throws MalformedURLException {
        FlatFileItemReader<TickerData> reader = new FlatFileItemReader<TickerData>();
        reader.setResource(new UrlResource("http://finance.yahoo.com/d/quotes.csv?s=XOM+IBM+JNJ+MSFT&f=snd1ol1p2"));
        reader.setLineMapper(new DefaultLineMapper<TickerData>() {{
            setLineTokenizer(new DelimitedLineTokenizer());
            setFieldSetMapper(new TickerFieldSetMapper());
        }});
        return reader;
    }

ItemProcessor и ItemWriter ранее использовали аннотацию @Component для контейнера Spring, чтобы взять и загрузить bean-компонент в контекст приложения.

1
2
3
4
5
6
7
8
9
@Bean
    public ItemProcessor<TickerData, TickerData> processor() {
        return new TickerPriceProcessor();
    }
 
    @Bean
    public ItemWriter<TickerData> writer() {
        return new LogItemWriter();
    }

Теперь, когда мы определили наши bean-компоненты Spring, мы можем создать аннотированные методы @Bean, которые представляют шаг и задание. Для справки я включил соответствующую конфигурацию XML.

1
2
3
4
5
6
7
8
9
<batch:job id="TickerPriceConversion">
        <batch:step id="convertPrice">
            <batch:tasklet transaction-manager="transactionManager">
                <batch:chunk reader="tickerReader"
                            processor="tickerPriceProcessor"
                        writer="tickerWriter" commit-interval="10" />
                </batch:tasklet>
            </batch:step>
    </batch:job>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Bean
    public Job TickerPriceConversion() throws MalformedURLException {
        return jobs.get("TickerPriceConversion").start(convertPrice()).build();
    }
 
    @Bean
        public Step convertPrice() throws MalformedURLException {
        return steps.get("convertPrice")
                .<TickerData, TickerData> chunk(5)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
        }

Я включу полный код для класса TickerPriceConversionConfig в конце статьи для справки, но в основном это все, что нужно сделать!

После того как вы определили свои bean-компоненты Spring и использовали JobBuilderFactory и StepBuilderFactory для создания конфигурации bean-компонентов для пакетного задания и шага, вы готовы запустить задание и протестировать его. Для запуска задания мы будем использовать Spring Boot для проверки выполнения недавно преобразованной конфигурации задания. Для этого мы создадим новый класс в тестовом пакете с именем TickerPriceConversionJobRunner.

Исходный код выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.keyhole.example;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class TickerPriceConversionJobRunner {
 
    public static void main(String[] args) {
        SpringApplication.run(TickerPriceConversionJobRunner.class, args);
    }
 
}

Аннотация @SpringBootApplication — это, по сути, удобная аннотация, предоставляющая функции, которые вы обычно получаете, используя @Configuration, @EnableAutoConfiguration и @ComponentScan. TickerPriceConversionJobRunner — это простое Java-приложение, которое делегирует обработку основного метода классу SpringAot SpringApplication для запуска приложения.

Теперь вы можете экспортировать этот проект в виде jar и запустить TickerPriceConversionJobRunner из командной строки или, если вы хотите запустить его в Spring STS, вы можете щелкнуть правой кнопкой мыши по классу и выбрать «Запуск от имени» → Spring Boot Application.

Заключительные мысли и списки кодов

Как видите, для создания конфигураций заданий Spring Batch не требуется много работы, но если вы решите преобразовать все свои существующие задания из конфигурации на основе XML в более новую конфигурацию на основе JavaConfig, у вас будет довольно впереди немного работы. Большая часть этой работы будет связана с количеством времени, необходимого для адекватного регрессионного тестирования пакетных заданий, которые вы преобразовали.

Будет ли это того стоить, если у вас есть обширная библиотека рабочих мест Spring Batch? Вероятно, нет, но если вы только начинаете или у вас есть управляемая библиотека пакетных заданий, это, безусловно, тот подход, который я бы выбрал.

Листинг кода для TickerPriceConversionConfig

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.keyhole.example.config;
 
import java.net.MalformedURLException;
 
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.UrlResource;
 
import com.keyhole.example.LogItemWriter;
import com.keyhole.example.TickerData;
import com.keyhole.example.TickerFieldSetMapper;
import com.keyhole.example.TickerPriceProcessor;
 
@Configuration
@EnableBatchProcessing
public class TickerPriceConversionConfig {
 
    @Autowired
    private JobBuilderFactory jobs;
 
    @Autowired
    private StepBuilderFactory steps;
 
    @Bean
    public ItemReader<TickerData> reader() throws MalformedURLException {
        FlatFileItemReader<TickerData> reader = new FlatFileItemReader<TickerData>();
        reader.setResource(new UrlResource("http://finance.yahoo.com/d/quotes.csv?s=XOM+IBM+JNJ+MSFT&f=snd1ol1p2"));
        reader.setLineMapper(new DefaultLineMapper<TickerData>() {{
            setLineTokenizer(new DelimitedLineTokenizer());
            setFieldSetMapper(new TickerFieldSetMapper());
        }});
        return reader;
    }
 
    @Bean
    public ItemProcessor<TickerData, TickerData> processor() {
        return new TickerPriceProcessor();
    }
 
    @Bean
    public ItemWriter<TickerData> writer() {
        return new LogItemWriter();
    }
 
    @Bean
    public Job TickerPriceConversion() throws MalformedURLException {
        return jobs.get("TickerPriceConversion").start(convertPrice()).build();
    }
 
    @Bean
    public Step convertPrice() throws MalformedURLException {
        return steps.get("convertPrice")
                .<TickerData, TickerData> chunk(5)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }
}

Листинг кода для TickerPriceConversionJobRunner

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.keyhole.example;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class TickerPriceConversionJobRunner {
 
    public static void main(String[] args) {
        SpringApplication.run(TickerPriceConversionJobRunner.class, args);
    }
 
}