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 と同じ他の機能を利用できることを意味します。

その方法で注入する依存関係が、最も単純な種類のものであることを確認してください。@Configuration クラスはコンテキストの初期化中に非常に早い段階で処理され、このように依存関係を強制的に注入すると、予期しない初期初期化が発生する可能性があります。前の例のように、可能な場合はいつでもパラメーターベースの注入に頼ってください。

同じ構成クラスの @PostConstruct メソッド内でローカルに定義された Bean へのアクセスは避けてください。非静的 @Bean メソッドでは意味的に完全に初期化された構成クラスインスタンスを呼び出す必要があるため、これは実質的に循環参照につながります。循環参照が禁止されている場合 (Spring Boot 2.6+ など)、これにより BeanCurrentlyInCreationException がトリガーされる可能性があります。

また、@Bean を介した BeanPostProcessor および BeanFactoryPostProcessor の定義には特に注意してください。通常、これらは static @Bean メソッドとして宣言する必要があり、含まれる構成クラスのインスタンス化をトリガーしません。そうでない場合、@Autowired および @Value は、AutowiredAnnotationBeanPostProcessor (Javadoc) よりも前に 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)
	}

}
properties-config.xml
<beans>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</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 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>()
	// ...
}