kagamihogeの日記

kagamihogeの日記です。

spring-batchのBatchConfigurerのあたりをまなぶ

目的

たとえば、spring-batchで自前のPlatformTransactionManagerにカスタマイズしたい場合、自前のBatchConfigurerのbean定義を作成すれば有効になる。このへんの仕組みを知るために、その周辺のソースコードを読んでその動作をまなぶ。いわゆる個人の日記レベルのお勉強メモ。

コードを読む

pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-batch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
    </dependencies>

EnableBatchProcessing

まず、Spring Batchの各種beanを自動的に登録するために以下のアノテーションを使用する。このアノテーションは以下のようになっている。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {

ここでは@Import(BatchConfigurationSelector.class)の通り、さらに別のconfigクラスを読み込んでいる。Selectorの名前の通り、何らかの条件に基づいて読み込むconfigクラスを決定している。では、その条件とは何か。

BatchConfigurationSelector

このクラスは以下のようにImportSelectorを実装している。このインタフェースは名前の通りで、読み込みたいconfigを動的に決定したい場合に使用する。

public class BatchConfigurationSelector implements ImportSelector {

で、その動的に決定する条件を以下のメソッドで記述している。

 @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
                // ...(略)
        if (attributes.containsKey("modular") && attributes.getBoolean("modular")) {
            imports = new String[] { ModularBatchConfiguration.class.getName() };
        }
        else {
            imports = new String[] { SimpleBatchConfiguration.class.getName() };
        }

特に何も指定しなければSimpleBatchConfigurationを使用する。もしくは、EnableBatchProcessing#modulartrueの場合はModularBatchConfigurationを使用する、という仕組み。とりあえず後者は置いておく。

SimpleBatchConfiguration

ここまでの記述で特に何もオプションを指定しなければSimpleBatchConfigurationを使用することがわかった。このクラスは以下のような宣言になっており、フツーのJava configクラスになっている。

@Configuration
public class SimpleBatchConfiguration extends AbstractBatchConfiguration {

先に親クラスであるAbstractBatchConfigurationを見ていく。とりあえず、@Autowired@Beanがついてるところを抜粋する。

@Configuration
@Import(ScopeConfiguration.class)
public abstract class AbstractBatchConfiguration implements ImportAware {
    @Autowired(required = false)
    private DataSource dataSource;

    @Bean
    public JobBuilderFactory jobBuilders() throws Exception {

    @Bean
    public StepBuilderFactory stepBuilders() throws Exception {

    @Bean
    public abstract JobRepository jobRepository() throws Exception;

    @Bean
    public abstract JobLauncher jobLauncher() throws Exception;

    @Bean
    public abstract JobExplorer jobExplorer() throws Exception;

    @Bean
    public JobRegistry jobRegistry() throws Exception {

    @Bean
    public abstract PlatformTransactionManager transactionManager() throws Exception;

    protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers) throws Exception {

@Configuration
class ScopeConfiguration {

    @Bean
    public static StepScope stepScope() {

    @Bean
    public static JobScope jobScope() {
}

spring-batchではお馴染みというか必須のJobRepositoryJobLauncherといったbean定義が並んでいる。特記事項としてはScopeConfigurationで、これによって@StepScope@JobScopeを使用可能にしている。

SimpleBatchConfigurationAbstractBatchConfigurationを継承した具象クラスでabstractに実装を与えている。ここまでの流れで、EnableBatchProcessingアノテーションを付与するとspring-batchの各種beanが自動的に登録される、までが分かった。

BatchConfigurer

少し話は変わり、例えばトランザクションマネージャを自前のものにカスタマイズしたい、というケースをみていく。マニュアル https://docs.spring.io/spring-batch/4.1.x/reference/html/job.html#javaConfig とかぐぐると出てくるのだが、BatchConfigurer(とDefaultBatchConfigurer)を使う、という情報が出てくる。以下は公式マニュアルからの抜粋。

@Bean
public BatchConfigurer batchConfigurer() {
        return new DefaultBatchConfigurer() {
                @Override
                public PlatformTransactionManager getTransactionManager() {
                        return new MyTransactionManager();
                }
        };
}

BatchConfigurerのbeanを返すとなぜカスタマイズが出来るのか。

仕組みの前にまずBatchConfigurerインタフェースの定義を確認する。

public interface BatchConfigurer {
    JobRepository getJobRepository() throws Exception;
    PlatformTransactionManager getTransactionManager() throws Exception;
    JobLauncher getJobLauncher() throws Exception;
    JobExplorer getJobExplorer() throws Exception;
}

雰囲気的にも確かにここが拡張ポイントなんだな、というのが分かる。

SimpleBatchConfiguration#initialize

BatchConfigurerを取得するところはどこかというと、SimpleBatchConfiguration.initializeで、BatchConfigurerからgetしたものを自分自身のプロパティにsetしている。そして、それを@Beanの戻り値として使用する。

   protected void initialize() throws Exception {
        if (initialized) {
            return;
        }
        BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());
        jobRepository.set(configurer.getJobRepository());
        jobLauncher.set(configurer.getJobLauncher());
        transactionManager.set(configurer.getTransactionManager());
        jobRegistry.set(new MapJobRegistry());
        jobExplorer.set(configurer.getJobExplorer());
        initialized = true;
    }
   private AtomicReference<JobRepository> jobRepository = new AtomicReference<JobRepository>();

    @Override
    @Bean
    public JobRepository jobRepository() throws Exception {
        return createLazyProxy(jobRepository, JobRepository.class);
    }

initializeをどこから呼んでいるかとか、createLazyProxyとか、はとりあえず置いておく。

ひとまずBatchConfigurerSimpleBatchConfiguration@Beanに使われる、そのためBatchConfigurerをカスタマイズすればそっちのbeanが使われる、という仕組みな事が分かる。

AbstractBatchConfiguration#getConfigurer

自前のBatchConfigurerを登録する場合はともかく、登録してない場合のデフォルト動作はどうなっているのだろうか。上記のSimpleBatchConfiguration.initializeが呼ぶgetConfigurerがそれなので、このメソッドの挙動を見てみる。

   protected BatchConfigurer getConfigurer(Collection<BatchConfigurer> configurers) throws Exception {
            // (略)
        if (configurers == null || configurers.isEmpty()) {
            if (dataSource == null) {
                DefaultBatchConfigurer configurer = new DefaultBatchConfigurer();
                configurer.initialize();
                this.configurer = configurer;
                return configurer;
            } else {
                DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource);
                configurer.initialize();
                this.configurer = configurer;
                return configurer;
            }
            // (略)
        this.configurer = configurers.iterator().next();

まず、自前のBatchConfigurerをbean定義する場合は、以下のような呼び出し方になっているので、getConfigurerの引数にそのbeanが入る。

   protected void initialize() throws Exception {
                // (略)
        BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());

で、最終行のthis.configurer = configurers.iterator().next();を通過し、そのconfigが使われる事になる。

次に、自前のBatchConfigurerが無い場合。こちらは、dataSourceのbean定義の有無で若干挙動が変化する。どちらのケースでもDefaultBatchConfigurerを使うことに変わりはないが、使用するコンストラクタが異なる。

ここまでの流れで、特に何もしなければBatchConfigurerのデフォルト実装DefaultBatchConfigurerが各種beanのインスタンス生成を行い、もしカスタマイズしたければBatchConfigurerを拡張するbean定義をすればそれが自動的に使用される、ということが分かる。

ただし、spring-bootではBatchConfigurerを自前で設定しない場合にDefaultBatchConfigurerが使われるわけではない。

DefaultBatchConfigurer

このクラスは名前のとおりJobRepositoryPlatformTransactionManagerなどのspring-batchで必要となるbeanの実際のインスタンス生成を一通り行う……が、ひとまずこのクラスの中見るのは後回し。なんでかというとspring-bootでなんも設定しないとこのクラスではなく下記のBasicBatchConfigurerが使われるため。また、やってる事は後述のBasicBatchConfigurerはほとんど変わらない。

BasicBatchConfigurer

spring-bootでは(SpringBootApplicationとか付与する場合)特になんも設定しないとorg.springframework.boot.autoconfigure.batch.BasicBatchConfigurerが使われる。

public class BasicBatchConfigurer implements BatchConfigurer {
    @PostConstruct
    public void initialize() {
              // 例外処理省略
            this.transactionManager = buildTransactionManager();
            this.jobRepository = createJobRepository();
            this.jobLauncher = createJobLauncher();
            this.jobExplorer = createJobExplorer();
    }

    protected JobExplorer createJobExplorer() throws Exception {
        JobExplorerFactoryBean factory = new JobExplorerFactoryBean();
                //  略

    protected JobLauncher createJobLauncher() throws Exception {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
                //  略    

    protected JobRepository createJobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
                //  略    

    protected PlatformTransactionManager createTransactionManager() {
        return new DataSourceTransactionManager(this.dataSource);
                //  略    

やってる事はシンプルでspring-batchを動作させるのに必要最小限のbean定義を行っている。DefaultBatchConfigurerとほとんどやってる事は変わらない。では、このクラスが有効になる条件とは何か。

BatchConfigurerConfiguration

org.springframework.boot.autoconfigure.batch.BatchConfigurerConfigurationというクラスがあり、このクラスは以下のようになっている。

@ConditionalOnClass(PlatformTransactionManager.class)
@ConditionalOnMissingBean(BatchConfigurer.class)
@Configuration
class BatchConfigurerConfiguration {

詳細は省略するが@SpringBootApplicationを付与するとその内部のcomponentScanの結果により、上記configクラスがscan対象になる。そして、クラスパスにPlatformTransactionManagerが存在し、BatchConfigurerのbean定義が無い場合にこのconfigが有効になる。つまり、自前のBatchConfigurerのbean定義が有ればこのconfigは使われない。無い場合はこのconfigが使用され、このクラスが作成するBasicBatchConfigurerのbean定義が使われる。

BasicBatchConfigurerを作ってる箇所は以下のようになっている。

   @Configuration
    @ConditionalOnMissingBean(name = "entityManagerFactory")
    static class JdbcBatchConfiguration {
        @Bean
        public BasicBatchConfigurer batchConfigurer(BatchProperties properties,
                DataSource dataSource,
                ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
            return new BasicBatchConfigurer(properties, dataSource,
                    transactionManagerCustomizers.getIfAvailable());
        }
    }

このクラスにはJpaBatchConfigurerを返すbean定義も存在する。上記はentityManagerFactoryが無い場合に有効になり、JPAではない場合くらいの意味。JpaBatchConfigurerについてはとりあえず触れない。

それで、メソッドの引数のbeanはどこかで定義しておく必要がある。以下の通り。

  • BatchProperties - spring.batch.*で定義するやつ。spring-bootが自動的に作ってくれる。
  • DataSource - 説明不要。必須。
  • ObjectProvider<TransactionManagerCustomizers> - 使ったことなくて良くわからん。nullでもおk。

DataSourceのbean定義が必須で、必要に応じてspring.batch.*を定義する。ただ、H2とか組み込みDB使う場合はDataSourceの自動設定してくれるので、必須というのは正確ではない。

まとめ

  • 基本的にはBatchConfigurerはspring-bootが自動登録するBasicBatchConfigurerに任せれば良い。
  • もしカスタマイズが必要であれば、BatchConfigurerのbean定義を自前で作れば、こちらが優先される。