Java ベースの構成の作成
Spring の Java ベースの構成機能により、アノテーションを作成でき、構成の複雑さを軽減できます。
@Import
アノテーションの使用
<import/>
要素が Spring XML ファイル内で構成のモジュール化を支援するために使用されるのと同じように、@Import
アノテーションは、次の例が示すように、別の構成クラスから @Bean
定義をロードすることを可能にします。
Java
Kotlin
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
次の例に示すように、コンテキストをインスタンス化するときに ConfigA.class
と ConfigB.class
の両方を指定する必要はなく、ConfigB
のみを明示的に指定する必要があります。
Java
Kotlin
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
// now both beans A and B will be available...
val a = ctx.getBean<A>()
val b = ctx.getBean<B>()
}
このアプローチは、構築中に多数の @Configuration
クラスを覚えておく必要がなく、1 つのクラスのみを処理する必要があるため、コンテナーのインスタンス化を簡素化します。
Spring Framework 4.2 以降、@Import は、AnnotationConfigApplicationContext.register メソッドと同様に、通常のコンポーネントクラスへの参照もサポートします。これは、すべてのコンポーネントを明示的に定義するエントリポイントとしていくつかの構成クラスを使用することにより、コンポーネントのスキャンを避けたい場合に特に役立ちます。 |
インポートされた @Bean
定義への依存関係の注入
上記の例は機能しますが、単純です。最も実用的なシナリオでは、Bean は構成クラス間で相互に依存関係を持っています。XML を使用する場合、これは課題ではありません。コンパイラーが関与していないため、ref="someBean"
を宣言し、Spring を信頼してコンテナーの初期化中にそれを解決できるためです。@Configuration
クラスを使用する場合、Java コンパイラーは構成モデルに制約を課します。そのため、他の Bean への参照は有効な Java 構文でなければなりません。
幸いなことに、この問題の解決は簡単です。すでに説明したように、@Bean
メソッドには、Bean の依存関係を記述する任意の数のパラメーターを含めることができます。複数の @Configuration
クラスがあり、それぞれが他のクラスで宣言された Bean に依存する、次のようなより現実的なシナリオを考えてみましょう。
Java
Kotlin
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig {
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
同じ結果を達成する別の方法があります。@Configuration
クラスは、最終的にはコンテナー内の別の Bean にすぎないことに注意してください。これは、@Autowired
および @Value
インジェクション、および他の Bean と同じ他の機能を利用できることを意味します。
その方法で注入する依存関係が、最も単純な種類のものであることを確認してください。 同じ構成クラスの また、 |
次の例は、ある Bean を別の Bean にオートワイヤーする方法を示しています。
Java
Kotlin
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
@Configuration クラスでのコンストラクターの注入は、Spring Framework 4.3 以降でのみサポートされます。また、ターゲット Bean がコンストラクターを 1 つだけ定義している場合は、@Autowired を指定する必要がないことにも注意してください。 |
ナビゲーションを容易にする完全修飾インポートされた Bean
上記のシナリオでは、@Autowired
の使用はうまく機能し、望ましいモジュール性を提供しますが、オートワイヤーされた Bean 定義がどこで宣言されているかを正確に判断することは、まだいくらかあいまいです。例: ServiceConfig
を見ている開発者として、どのようにして @Autowired AccountRepository
Bean が宣言されているかを正確に知ることができますか? コードでは明示的ではありませんが、これで十分な場合があります。Pleiades All in One (JDK, STS, Lombok 付属) または Eclipse 用 Spring Tools (英語) には、すべての接続方法を示すグラフをレンダリングできるツールが用意されていることに注意してください。また、Java IDE は AccountRepository
型のすべての宣言と使用を簡単に見つけ、その型を返す @Bean
メソッドの場所をすばやく表示できます。
このあいまいさが許容されず、IDE 内で @Configuration
クラスから別のクラスに直接移動したい場合は、構成クラス自体のオートワイヤーを検討してください。次の例は、その方法を示しています。
Java
Kotlin
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
上記の状況では、AccountRepository
が定義されている場所は完全に明示的です。ただし、ServiceConfig
は RepositoryConfig
と密結合しています。それがトレードオフです。この密結合は、インターフェースベースまたは抽象クラスベースの @Configuration
クラスを使用することにより、ある程度緩和できます。次の例を考えてみましょう。
Java
Kotlin
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
@Configuration
interface RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(...)
}
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class) // import the concrete config!
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
ServiceConfig
は、具体的な DefaultRepositoryConfig
に関して疎結合されており、組み込みの IDE ツールは依然として有用です。RepositoryConfig
実装の型階層を簡単に取得できます。このようにして、@Configuration
クラスとその依存関係をナビゲートすることは、インターフェースベースのコードをナビゲートする通常のプロセスと変わりません。
特定の Bean の起動時の生成順序に影響を与えたい場合は、そのうちのいくつかを @Lazy (起動時ではなく最初のアクセス時に生成する) として宣言したり、他の特定の Bean を @DependsOn として宣言したりすることを検討してください。特定の他の Bean が現在の Bean よりも前に生成されることを確認し、後者の直接の依存関係が暗示する以上のことを行います。 |
@Configuration
クラスまたは @Bean
メソッドを条件付きで含める
任意のシステム状態に基づいて、完全な @Configuration
クラスまたは個々の @Bean
メソッドを条件付きで有効または無効にすると便利な場合があります。この一般的な例の 1 つは、@Profile
アノテーションを使用して、Spring Environment
で特定のプロファイルが有効になっている場合にのみ Bean をアクティブ化することです(詳細については Bean 定義プロファイルを参照)。
@Profile
アノテーションは、実際には @Conditional
(Javadoc) と呼ばれるはるかに柔軟なアノテーションを使用して実装されます。@Conditional
アノテーションは、@Bean
が登録される前に確認する必要がある特定の org.springframework.context.annotation.Condition
実装を示します。
Condition
インターフェースの実装は、true
または false
を返す matches(…)
メソッドを提供します。例: 次のリストは、@Profile
に使用される実際の Condition
実装を示しています。
Java
Kotlin
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().matchesProfiles((String[]) value)) {
return true;
}
}
return false;
}
return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.matchesProfiles(*value as Array<String>)) {
return true
}
}
return false
}
return true
}
詳細については、@Conditional
javadoc を参照してください。
Java と XML 構成の組み合わせ
Spring の @Configuration
クラスのサポートは、Spring XML の 100% の完全な代替になることを目指しているわけではありません。Spring XML 名前空間のようないくつかの機能は、コンテナーを構成するための理想的な方法であり続けます。XML が便利な場合や必要な場合には、ClassPathXmlApplicationContext
などを使用して「XML 中心」の方法でコンテナーをインスタンス化するか、AnnotationConfigApplicationContext
と必要に応じて XML をインポートする @ImportResource
アノテーションを使用して「Java 中心」の方法でコンテナーをインスタンス化するかのいずれかを選択することができます。
@Configuration
クラスの XML 中心の使用
XML から Spring コンテナーをブートストラップし、@Configuration
クラスをアドホックな方法で含めることが望ましい場合があります。例: Spring XML を使用する大規模な既存のコードベースでは、@Configuration
クラスを必要に応じて作成し、既存の XML ファイルから含める方が簡単です。このセクションの後半では、この種の「XML 中心」の状況で @Configuration
クラスを使用するためのオプションについて説明します。
@Configuration
クラスをプレーンな Spring <bean/>
要素として宣言する
@Configuration
クラスは、最終的にはコンテナー内の Bean 定義であることに注意してください。この一連の例では、AppConfig
という名前の @Configuration
クラスを作成し、それを <bean/>
定義として system-test-config.xml
内に含めます。<context:annotation-config/>
がオンになっているため、コンテナーは @Configuration
アノテーションを認識し、AppConfig
で宣言された @Bean
メソッドを適切に処理します。
次の例は、Java および Kotlin の AppConfig
構成クラスを示しています。
Java
Kotlin
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
}
@Configuration
class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
次の例は、サンプル system-test-config.xml
ファイルの一部を示しています。
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
次の例は、可能な jdbc.properties
ファイルを示しています。
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
Java
Kotlin
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
system-test-config.xml ファイルでは、AppConfig <bean/> は id 属性を宣言していません。宣言しても問題ありませんが、他の Bean がこれを参照することはなく、名前でコンテナーから明示的に取得されることもほとんどないため、宣言する必要はありません。同様に、DataSource Bean は型によってのみ自動接続されるため、明示的な Bean id は厳密には必要ありません。 |
<context:component-scan/> を使用して @Configuration
クラスを取得する
@Configuration
は @Component
でメタアノテーションが付けられているため、@Configuration
アノテーションが付けられたクラスは自動的にコンポーネントスキャンの候補になります。前の例で説明したのと同じシナリオを使用して、コンポーネントスキャンを利用するために system-test-config.xml
を再定義できます。この場合、<context:component-scan/>
は同じ機能を有効にするため、明示的に <context:annotation-config/>
を宣言する必要がないことに注意してください。
次の例は、変更された system-test-config.xml
ファイルを示しています。
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
@ImportResource
での XML のクラス中心の使用
@Configuration
クラスがコンテナーを構成するための主要なメカニズムであるアプリケーションでは、少なくとも一部の XML を使用する必要がある場合があります。このようなシナリオでは、@ImportResource
を使用して、必要なだけの XML を定義できます。これにより、コンテナーを構成するための「Java 中心」のアプローチが実現され、XML が最小限に抑えられます。次の例 (構成クラス、Bean を定義する XML ファイル、プロパティファイル、main()
メソッドを含む) は、必要に応じて XML を使用する「Java 中心」の構成を実現するために @ImportResource
アノテーションを使用する方法を示しています。
Java
Kotlin
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
Java
Kotlin
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}