Spring 4 以降に
@Conditionalなどの条件注釈が導入され、これは Spring Boot における自動構成の最大の功労者です!
さて、問題です:もし私たちがまだ Spring 3.x の古いバージョンを使用している場合、どのように自動構成を実現すればよいのでしょうか?
コードは GitHub にホストされています。スターを歓迎します 😘
要求と問題#
コアの要求#
- 現存システムはリファクタリングしない
- Spring バージョンは 3.x で、バージョンアップや Spring Boot の導入は考えていない
- コードをあまり変更せずに機能強化を実現したい
例えば:
- サイト全体にログ記録を統一して追加したい(例:RPC フレームワークの Web 呼び出しの要約情報、データベースアクセス層の要約情報)、これは実際には一般的な機能です。
- 基盤となるインフラをいくつか引用し、これらのインフラの機能をさらに強化したい。この場合、フレームワークのレベルからこの問題を解決する必要があります。
直面している問題#
-
3.x の Spring には条件注釈がない
条件注釈がないため、いつこれらのものを構成する必要があるか、またはないかが不明です。
-
自動構成を自動的に特定できない
この時点で、Spring Boot の自動構成のようにフレームワークが自動的に私たちの構成を読み込むことはできません。私たちは他の手段を使って Spring が私たちのカスタマイズした機能を読み込むことができるようにする必要があります。
コアの解決思路#
条件判断#
BeanFactoryPostProcessorを通じて判断
Spring は私たちに拡張ポイントを提供しており、BeanFactoryPostProcessor を通じて条件判断の問題を解決できます。これにより、BeanFactory の定義が完了した後、Bean の初期化前にこれらの Bean の定義に対して後処理を行うことができます。この時点で、現在存在する / 不足している Bean の定義を判断し、カスタムの Bean を追加することもできます。
構成の読み込み#
- Java Config クラスを作成
- 構成クラスを導入
component-scanを通じて- XML ファイル
importを通じて
自分の Java Config クラスを作成し、それを component-scan に追加することを検討できます。また、現在のシステムが XML の方式を使用している場合、その読み込みパスで私たちの XML ファイルを読み込むことができるかどうかを確認し、できない場合は手動でこのファイルを import することができます。
Spring が提供する二つの拡張ポイント#
BeanPostProcessor#
- Bean インスタンスに対して
- Bean 作成後にカスタムロジックのコールバックを提供
BeanFactoryPostProcessor#
- Bean 定義に対して
- コンテナが Bean を作成する前に構成メタデータを取得
- Java Config では
staticメソッドとして定義する必要があります(定義しない場合、Spring の起動時にwarningが表示されますので、試してみてください)
Bean に関するいくつかのカスタマイズ#
上記で Spring の二つの拡張ポイントについて言及したので、ここでは Bean に関するいくつかのカスタマイズ方法を展開します。
ライフサイクルコールバック#
-
InitializingBean/@PostConstruct/init-methodこれは初期化に関する部分で、Bean の初期化後にカスタマイズを行うことができます。ここには三つの方法があります:
InitializingBeanインターフェースを実装@PostConstruct注釈を使用- Bean 定義の XML ファイルで
init-methodを指定;または@Bean注釈を使用する際にinit-methodを指定
これらはすべて、Bean が作成された後に特定のメソッドを呼び出すことを可能にします。
-
DisposableBean/@PreDestroy/destroy-methodこれは Bean が回収される際に行うべき操作です。この Bean が破棄される際に:
DisposableBeanインターフェースを実装している場合、Spring はそのメソッドを呼び出します- また、
@PreDestroy注釈を特定のメソッドに追加すると、そのメソッドが破棄時に呼び出されます - Bean 定義の XML ファイルで
destroy-methodを指定;または@Bean注釈を使用する際にdestroy-methodを指定すると、そのメソッドが破棄時に呼び出されます
XxxAware インターフェース#
-
ApplicationContextAwareインターフェースを通じて全体の
ApplicationContextを注入でき、この Bean 内で完全なApplicationContextを取得できます。 -
BeanFactoryAwareApplicationContextAwareと似ています。 -
BeanNameAwareBean の名前をこのインスタンスに注入できます。
ソースコードに興味がある方は、
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBeanを参照してください。
現在の Bean にcloseまたはshutdownメソッド名のメソッドが存在する場合、Spring はそれをdestroy-methodと見なし、破棄時に呼び出します。
一部の一般的な操作#
クラスが存在するかどうかの判断#
ClassUitls.isPresent()
Spring が提供する ClassUitls.isPresent() を呼び出して、クラスが現在の Class Path に存在するかどうかを判断します。
Bean が定義されているかどうかの判断#
ListableBeanFactory.containsBeanDefinition():Bean が定義されているかどうかを判断します。ListableBeanFactory.getBeanNamesForType():特定のタイプの Bean がどのような名前で定義されているかを確認できます。
Bean 定義の登録#
BeanDefinitionRegistry.registerBeanDefinition()GenericBeanDefinition
BeanFactory.registerSingleton()
袖をまくって頑張ろう#
理論はこれで終わりです。
次は実践に移ります。
現在の例では、Spring Boot や高バージョンの Spring を使用していない環境を仮定します。
ステップ 1:低バージョンの Spring 環境を模擬する
ここでは単純に spring-context 依存関係を導入しただけで、実際に Spring 3.x のバージョンを使用しているわけではありませんが、Spring 4 以上のいくつかの機能も使用していません。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
ステップ 2:BeanFactoryPostProcessor インターフェースの実装例
@Slf4j
public class GreetingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// 現在の Class Path に GreetingApplicationRunner というクラスが存在するかどうかを判断
boolean hasClass = ClassUtils
.isPresent("io.github.y0ngb1n.samples.greeting.GreetingApplicationRunner",
GreetingBeanFactoryPostProcessor.class.getClassLoader());
if (!hasClass) {
// クラスが存在しない
log.info("GreetingApplicationRunner は CLASSPATH に存在しません。");
return;
}
// id が greetingApplicationRunner の Bean 定義が存在するかどうか
boolean hasDefinition = beanFactory.containsBeanDefinition("greetingApplicationRunner");
if (hasDefinition) {
// 現在のコンテキストに greetingApplicationRunner が既に存在する
log.info("既に greetingApplicationRunner Bean が登録されています。");
return;
}
register(beanFactory);
}
private void register(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(GreetingApplicationRunner.class);
((BeanDefinitionRegistry) beanFactory)
.registerBeanDefinition("greetingApplicationRunner", beanDefinition);
} else {
beanFactory.registerSingleton("greetingApplicationRunner", new GreetingApplicationRunner());
}
}
}
私たちの Bean を登録します(見てください CustomStarterAutoConfiguration)。以下の点に注意が必要です:
- ここでのメソッドは
staticとして定義されています - 使用する際、二つのプロジェクトが同じパッケージにない場合は、現在のクラスをプロジェクトの
component-scanに追加する必要があります
@Configuration
public class CustomStarterAutoConfiguration {
@Bean
public static GreetingBeanFactoryPostProcessor greetingBeanFactoryPostProcessor() {
return new GreetingBeanFactoryPostProcessor();
}
}
ステップ 3:この自動構成が有効かどうかを検証
他のプロジェクトに依存関係を追加します:
<dependencies>
...
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-spring-lt4-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.github.y0ngb1n.samples</groupId>
<artifactId>custom-starter-core</artifactId>
</dependency>
...
</dependencies>
プロジェクトを起動し、ログを観察します(見てください custom-starter-examples)、自動構成が有効かどうかを検証します:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.0.RELEASE)
2019-05-02 20:47:27.692 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : AutoconfigureDemoApplication を HP で PID 11460 で起動中 ...
2019-05-02 20:47:27.704 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : アクティブなプロファイルが設定されていないため、デフォルトプロファイルにフォールバックします:default
2019-05-02 20:47:29.558 INFO 11460 --- [ main] i.g.y.s.g.GreetingApplicationRunner : GreetingApplicationRunner を初期化中。
2019-05-02 20:47:29.577 INFO 11460 --- [ main] i.g.y.s.d.AutoconfigureDemoApplication : 3.951 秒で AutoconfigureDemoApplication を起動しました(JVM は 14.351 秒間実行中)
2019-05-02 20:47:29.578 INFO 11460 --- [ main] i.g.y.s.g.GreetingApplicationRunner : 皆さんこんにちは!私たちは皆 Spring が大好きです!
これで、低バージョンの Spring で自動構成に似た機能を成功裏に実現しました。👏