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
を取得できます。 -
BeanFactoryAware
ApplicationContextAware
と似ています。 -
BeanNameAware
Bean の名前をこのインスタンスに注入できます。
ソースコードに興味がある方は、
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 で自動構成に似た機能を成功裏に実現しました。👏