目的
たとえば、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定義を自前で作れば、こちらが優先される。