目的
たとえば、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#modular
がtrue
の場合は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ではお馴染みというか必須のJobRepository
やJobLauncher
といったbean定義が並んでいる。特記事項としてはScopeConfiguration
で、これによって@StepScope
や@JobScope
を使用可能にしている。
SimpleBatchConfiguration
はAbstractBatchConfiguration
を継承した具象クラスで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とか、はとりあえず置いておく。
ひとまずBatchConfigurer
がSimpleBatchConfiguration
の@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
このクラスは名前のとおりJobRepository
やPlatformTransactionManager
などの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定義を自前で作れば、こちらが優先される。