BeanCreationExceptionで最初に確認すること
起動ログの先頭に invoiceJob が出ても、直すのがそのBeanとは限りません。請求バッチの例では、invoiceJob が使う invoiceRules は登録済みですが、その初期化処理が設定値 0 を拒否します。ログから、依存側のBean・実際に失敗したBean・最深の原因を分けます。
実行用サンプル
Bean生成時に初期化が失敗するケースと、同じBean定義のまま設定値を直すケースをJUnitで確認できます。Spring BootやWebサーバーは起動しません。
JDK 17、Maven 3.9系、Spring Framework 6.2.19。ZIPにはソース全文、pom.xml、READMEを収録しています。展開先で mvn test を実行します。
要約
まず先頭から「起動の途中でどのBeanが作れなくなったか」を控えます。次に Caused by を最後までたどり、例外名と入力値を拾います。今回開くのは invoiceRules の初期化処理と invoice.batch-size の設定です。
エラー例
invoiceJob の生成時に invoiceRules を受け取ります。Springが invoiceRules を作って initialize() を呼ぶと、値が 0 のため例外が出ます。次のログはJUnitの実行結果から4行を抜き出したものです。
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'invoiceJob' defined in example.BeanCreationCauseTest$Config: Unsatisfied dependency expressed through method 'invoiceJob' parameter 0: Error creating bean with name 'invoiceRules' defined in example.BeanCreationCauseTest$Config: invoice.batch-size must be positive: 0
[…]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'invoiceRules' defined in example.BeanCreationCauseTest$Config: invoice.batch-size must be positive: 0
[…]
Caused by: java.lang.IllegalArgumentException: invoice.batch-size must be positive: 0
[…]
at example.BeanCreationCauseTest$InvoiceRules.initialize(BeanCreationCauseTest.java:66)BeanCreationCauseTest.java の全文です。
package example;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.MapPropertySource;
import static org.junit.jupiter.api.Assertions.*;
class BeanCreationCauseTest {
@Test
void findsTheFailedBeanAndInvalidValue() {
try (var context = createContext(0)) {
var outer = assertThrows(UnsatisfiedDependencyException.class, context::refresh);
assertEquals("invoiceJob", outer.getBeanName());
var failed = assertInstanceOf(BeanCreationException.class, outer.getCause());
assertEquals("invoiceRules", failed.getBeanName());
var root = assertInstanceOf(IllegalArgumentException.class, failed.getCause());
assertEquals("invoice.batch-size must be positive: 0", root.getMessage());
assertEquals("initialize", root.getStackTrace()[0].getMethodName());
outer.printStackTrace(System.out);
}
}
@Test
void startsWhenTheSamePropertyHasAValidValue() {
try (var context = createContext(20)) {
context.refresh();
assertEquals(20, context.getBean(InvoiceJob.class).rules().batchSize());
}
}
private AnnotationConfigApplicationContext createContext(int batchSize) {
var context = new AnnotationConfigApplicationContext();
context.getEnvironment().getPropertySources().addFirst(
new MapPropertySource("example", Map.of("invoice.batch-size", batchSize)));
context.register(Config.class);
return context;
}
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
InvoiceJob invoiceJob(InvoiceRules rules) {
return new InvoiceJob(rules);
}
@Bean(initMethod = "initialize")
InvoiceRules invoiceRules(@Value("${invoice.batch-size}") int batchSize) {
return new InvoiceRules(batchSize);
}
}
record InvoiceJob(InvoiceRules rules) {}
record InvoiceRules(int batchSize) {
public void initialize() {
if (batchSize < 1) {
throw new IllegalArgumentException(
"invoice.batch-size must be positive: " + batchSize);
}
}
}
}修正例
このバッチは1件以上をまとめて処理する仕様なので、invoice.batch-size を 0 から 20 に直します。サンプルの成功ケースは設定値だけを変更し、コンテキストが起動し、invoiceJob から参照した値が20であることを確認します。JUnitは両ケースを含む2件です。
実環境では、先に対象環境で有効なプロファイルを確認します。設定ファイルと環境変数など複数箇所に値がある場合は、どの設定から 0 が採用されたかまで特定します。入力検証を外して起動だけ通すと、処理件数の誤りが後の実行まで残ります。
詳細
なぜそうなるのか?
SpringはBeanを生成した後、設定された初期化メソッドを呼びます。このサンプルでは @Bean(initMethod = "initialize") を使います。初期化が例外で終わると、対象のBeanと、それに依存するBeanの生成も失敗します。SpringのBean初期化コールバック
仕組みのポイント
最深の例外が候補0件を示す場合は、その時点で登録経路を調べます。初期化メソッドの例外なら、先にその自作コードと入力を見ます。調査を引き継ぐときは「invoiceJob → invoiceRules → initialize() で設定値0を拒否」と、名前・処理・値を一緒に残します。



