組合基於 Java 的配置
Spring 的基於 Java 的配置功能允許您組合註解,這可以降低配置的複雜性。
使用 @Import 註解
正如 Spring XML 檔案中使用 <import/> 元素來幫助模組化配置一樣,@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 類。
從 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:這意味著它們可以像任何其他 bean 一樣利用 @Autowired 和 @Value 注入以及其他功能。
|
確保您以這種方式注入的依賴項僅屬於最簡單的型別。 避免在同一配置類上的 此外,在使用 |
以下示例展示了一個 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")
}
請注意,如果目標 bean 只定義一個建構函式,則無需指定 @Autowired。 |
完全限定匯入的 bean 以便於導航
在前面的場景中,使用 @Autowired 效果很好並提供了所需的模組化,但確定自動裝配的 bean 定義究竟在哪裡宣告仍然有些模糊。例如,作為檢視 ServiceConfig 的開發人員,您如何確切知道 @Autowired AccountRepository bean 在哪裡宣告?這在程式碼中並不明確,這可能很好。請記住,Spring Tools for Eclipse 提供了可以渲染顯示所有連線方式的圖表的工具,這可能就是您所需要的一切。此外,您的 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 定義的單例的啟動
如果您想影響某些單例 bean 的啟動建立順序,請考慮將其中一些宣告為 @Lazy,以便在首次訪問時而不是在啟動時建立。
@DependsOn 強制某些其他 bean 首先初始化,確保在當前 bean 之前建立指定的 bean,超出後者直接依賴項所暗示的範圍。
後臺初始化
從 6.2 版本開始,有一個後臺初始化選項:@Bean(bootstrap=BACKGROUND) 允許將特定 bean 單獨用於後臺初始化,涵蓋上下文啟動時每個此類 bean 的整個 bean 建立步驟。
具有非延遲注入點的依賴 bean 會自動等待 bean 例項完成。所有常規的後臺初始化都強制在上下文啟動結束時完成。只有另外標記為 @Lazy 的 bean 允許稍後完成(直到第一次實際訪問)。
後臺初始化通常與依賴 bean 中的 @Lazy (或 ObjectProvider) 注入點一起使用。否則,當實際需要早期注入後臺初始化的 bean 例項時,主引導執行緒將阻塞。
這種併發啟動形式適用於單個 bean:如果此類 bean 依賴於其他 bean,它們需要已經初始化,要麼透過簡單地提前宣告,要麼透過 @DependsOn 強制在主引導執行緒中初始化,然後再觸發受影響 bean 的後臺初始化。
|
必須宣告一個型別為 引導執行器可以是僅用於啟動目的的有界執行器,也可以是也用於其他目的的共享執行緒池。 |
有條件地包含 @Configuration 類或 @Bean 方法
根據某些任意系統狀態,有條件地啟用或停用完整的 @Configuration 類甚至單個 @Bean 方法通常很有用。一個常見的例子是使用 @Profile 註解僅當在 Spring Environment 中啟用特定配置檔案時才啟用 bean(有關詳細資訊,請參閱Bean 定義配置檔案)。
@Profile 註解實際上是透過使用一個更靈活的註解來實現的,該註解名為@Conditional。@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。某些功能,例如 Spring XML 名稱空間,仍然是配置容器的理想方式。在 XML 方便或必要的情況下,您可以選擇:要麼透過使用 ClassPathXmlApplicationContext 等方式以“XML 中心”的方式例項化容器,要麼透過使用 AnnotationConfigApplicationContext 和 @ImportResource 註解根據需要匯入 XML,以“Java 中心”的方式例項化容器。
以 XML 為中心使用 @Configuration 類
從 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:///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:annotation-config/>,因為 <context:component-scan/> 啟用了相同的功能。
以下示例顯示了修改後的 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() 方法)展示瞭如何使用 @ImportResource 註解實現“Java 中心”配置,並根據需要使用 XML
-
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:///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>()
// ...
}