マネージドBeanでは、@PostConstruct
は通常のJavaオブジェクト・コンストラクターの後に呼び出されます。
通常のコンストラクタ自体ではなく、なぜ@PostConstruct
を使ってbeanで初期化するのですか?
なぜなら、コンストラクタが呼び出されたときには、Beanはまだ初期化されていないからです。つまり、依存関係は注入されません。 @PostConstruct
メソッドでは、Beanは完全に初期化されており、依存関係を使用できます。
これは、このメソッドがBeanのライフサイクル内で一度だけ呼び出されることを保証する規約であるためです。 Beanがその内部作業でコンテナによって複数回インスタンス化されることは起こりそうにありませんが、@PostConstruct
が一度だけ呼び出されることを保証します。
主な問題は、次のとおりです。
コンストラクタでは、依存関係の注入はまだ行われていません*
*明らかにコンストラクタインジェクションを除く
実世界の例:
public class Foo {
@Inject
Logger LOG;
@PostConstruct
public void fooInit(){
LOG.info("This will be printed; LOG has already been injected");
}
public Foo() {
LOG.info("This will NOT be printed, LOG is still null");
// NullPointerException will be thrown here
}
}
IMPORTANT:@PostConstruct
と@PreDestroy
は完全に Java 11では削除されました.
それらを使い続けるには、依存関係に javax.annotation-api JARを追加する必要があります。
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
// https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api
compile group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
クラスがコンストラクターですべての初期化を実行する場合、@PostConstruct
は確かに冗長です。
ただし、クラスのセッターメソッドを使用して依存関係が注入されている場合、クラスのコンストラクターはオブジェクトを完全に初期化できず、すべてのセッターメソッドが呼び出された後に初期化を実行する必要がある場合があるため、@PostConstruct
のユースケース。
次のシナリオを検討してください。
public class Car {
@Inject
private Engine engine;
public Car() {
engine.initialize();
}
...
}
Carはフィールドインジェクションの前にインスタンス化する必要があるため、コンストラクターの実行中はインジェクションポイントエンジンはまだnullのままであり、結果としてNullPointerExceptionが発生します。
この問題は、 JSR-330 Dependency Injection for Java コンストラクタ注入、またはJSR 250 Common Annotations for Java @PostConstructメソッドアノテーションのいずれかによって解決できます。
@ PostConstruct
JSR-250はJava SE 6に含まれているアノテーションの共通セットを定義しています。
PostConstructアノテーションは、初期化を実行するために依存性注入が行われた後に実行する必要があるメソッドで使用されます。このメソッドはクラスがインサービスになる前に呼び出さなければなりません。このアノテーションは依存性注入をサポートするすべてのクラスでサポートされなければなりません。
JSR-250チャップ2.5 javax.annotation.PostConstruct
@PostConstructアノテーションを使用すると、インスタンスがインスタンス化され、すべてのインジェクションが実行された後にメソッドの定義を実行することができます。
public class Car {
@Inject
private Engine engine;
@PostConstruct
public void postConstruct() {
engine.initialize();
}
...
}
コンストラクターで初期化を実行する代わりに、コードは@PostConstructでアノテーションが付けられたメソッドに移動されます。
ポストコンストラクトメソッドの処理は、@ PostConstructでアノテーションが付けられたすべてのメソッドを見つけて順番に呼び出すという簡単なことです。
private void processPostConstruct(Class type, T targetInstance) {
Method[] declaredMethods = type.getDeclaredMethods();
Arrays.stream(declaredMethods)
.filter(method -> method.getAnnotation(PostConstruct.class) != null)
.forEach(postConstructMethod -> {
try {
postConstructMethod.setAccessible(true);
postConstructMethod.invoke(targetInstance, new Object[]{});
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
});
}
構築後メソッドの処理は、インスタンス化と注入が完了した後に実行する必要があります。
また、ある種のプロキシやリモート処理が含まれていると、コンストラクタベースの初期化が意図したとおりに機能しません。
EJBが逆シリアル化されるたびに、そしてそれに対して新しいプロキシが作成されるたびに、ctが呼び出されます。