今回、Spring Boot + Spring Batch + Java 6 でバッチを実装する機会があったので、もろもろを備忘録としてまとめる。
開発環境
Spring Tool Suite v3.8.3 を使用。Spring Starter Project
からプロジェクトを作成する。
- Java Version 1.6
- Spring Boot 1.4.3
- I/O > Batch
Dependencies で Batch
をチェックしてプロジェクトを作成すると、spring-boot-starter-batch
の依存が追加される。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency>
プロジェクトを作成して、Run Spring Boot App
すると以下のエラーが出る。
... nested exception is java.lang.UnsupportedClassVersionError: org/apache/tomcat/jdbc/pool/DataSource : Unsupported major.minor version 51.0
Tomcat JDBC が Java 6 に対応していない模様。今回、Tomcat やデータベースを使う予定がなかったので、思い切って Tomcat JDBC を依存から除外する。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> <exclusions> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </exclusion> </exclusions> </dependency>
とりあえず、Java 6 で Spring Boot + Spring Batch が動作することを確認。
Tasklet
今回は割とシンプルなアプリケーションだったので、Tasklet のみで構成。main
メソッドからの流れは基本的に本家のクイックスタートの通り。> Spring Batch
プロパティファイル
今回、src/main/resources 下の application.properties の他に、エンドユーザが設定するプロパティファイルとして application-env.properties を用意しました。(アプリ内部で利用するプロパティとエンドユーザが設定するプロパティをファイルとして分割しようと思って)
application-env.properties は config ディレクトリに配置して、jar を実行するときに -Dspring.config.location
でプロパティファイルのパスを指定すれば読み込める模様。尚、Linux 環境では --spring.config.location
なので注意。これで30分くらいハマった…。
java -jar batch.jar -Dspring.config.location=config/application-env.properties
-Dspring.config.location
で外部のプロパティファイルを指定したとき、クラスパス上の application.properties
は読み込まれるのか不安でしたが、普通に読み込めました。このあたりは別エントリにまとめました。
Spring Boot で複数の @ConfigurationProperties のプロパティを読み込む - kntmr-blog
プロパティは @ConfigurationProperties
を付けた Bean 経由で読み込む。@ConfigurationProperties
は、カンマ区切りのプロパティを自動的に List<T>
に格納してくれたり、Bean Validation と組み合わせてプロパティをチェックできるので便利。
以下の依存を追加。(@NotBlank
とかを使おうとすると、STS が追加してくれる)
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
@NotNull
は javax.validation.constraints
パッケージで提供されているアノテーション。@NotBlank
, @NotEmpty
は Hibernate Validator が提供するアノテーション。それぞれのおおまかな違いは以下を参考にさせていただきました。
@NotNull/@NotEmpty/@NotBlankの違い - 見習いプログラミング日記
Hibernate Validator のバリデーションメッセージは、デフォルトでは「may not be empty」のような英語メッセージとなる。今回、バリデーションメッセージを日本語化するために ValidationMessages.properties を追加したが、ログに出力されるメッセージが文字化ける…。アノテーションの message 属性に直接日本語を書くと問題ないんだけど。
で、ソースコードに直接メッセージを書きたくなかったのと、「may not be empty」でもまぁ問題ないかなということで、バリデーションメッセージの日本語化は諦めることに。
Logback
ロギングは Logback を使う。Logbackを使う場合は src/main/resources 配下に logback-spring.xml を配置する。今回は、コンソールとファイルに同じ内容を出力して、ファイルサイズでローテートするようにしている。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE logback> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <!--<include resource="org/springframework/boot/logging/logback/file-appender.xml" />--> <property name="LOG_FILE" value="../logs/batch.log" /> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_FILE}</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${LOG_FILE}.%i</fileNamePattern> <minIndex>1</minIndex> <maxIndex>5</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>100KB</maxFileSize> </triggeringPolicy> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> </configuration>
メッセージプロパティ
メッセージプロパティを使う場合は、src/main/resources の application.properties に以下の設定を追加する。
spring.messages.basename=messages spring.messages.cache-seconds=-1 spring.messages.encoding=UTF-8
ソースコードからは MessageSource クラス経由で messages.properties のメッセージを取得する。
// MessageSource クラスを DI @Autowired private MessageSource messageSource; // メッセージを取得 messageSource.getMessage(code, args, Locale.JAPAN);
Apache Commons IO
今回、あるディレクトリ配下のファイルを読み込んでごにょごにょする、みたいな要件がありました。Java 7 以降であれば、Files クラスとか Paths クラスとか何かと便利な API が使えていいんですが、今回は如何せん Java 6 …。というわけで、Apache Commons IO 2.3 を使う。
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.3</version> </dependency>
あと、やっぱり try-with-resources が使えないのが地味にツライ。
パッケージング
パッケージングは mvn package
するだけ。
ステータスコード
今回、Spring Batch で作成した jar をシェルスクリプトからキックして、RETVAL=$?
でステータスコードを取得する想定でした。
当初、StepContribution#setExitStatus
メソッドに ExitStatus.COMPLETED
or ExitStatus.FAILED
を渡して正常終了か異常終了を判別してみたものの、異常終了の場合でもステータスコードが 0 になる…。
// 正常終了 contribution.setExitStatus(ExitStatus.COMPLETED); // 異常終了 contribution.setExitStatus(ExitStatus.FAILED);
というわけで、ChunkContext クラスから StepExecution を辿って、StepExecution#setStatus
メソッドに BatchStatus.COMPLETED
or BatchStatus.FAILED
を渡すことで正常終了か異常終了を判別することができた。
ちなみに BatchStatus.FAILED
を渡すと、ステータスコードに 5 が返る。
// 正常終了 chunkContext.getStepContext().getStepExecution().setStatus(BatchStatus.COMPLETED); // 異常終了 chunkContext.getStepContext().getStepExecution().setStatus(BatchStatus.FAILED);
ちなみに本家のクイックスタートにも記載されているが、ステータスコードを返すときは main
メソッドで以下のようにして System#exit
を呼び出す。
public static void main(String[] args) { System.exit(SpringApplication.exit(SpringApplication.run(FooBatchApplication.class, args))); }