1.12 基於Java容器配置
這個部分涵蓋了在你的Java代碼中怎樣去使用註解配置Spring容器。它包含下面的主題:
- 基本概念:
@Bean
和@Configuration
- 使用
AnnotationConfigApplicationContext
實例化Spring容器 - 使用
@Bean
註解 - 使用
@Configuration
註解 - 基於Java的配置組合
- Bean定義配置文件
PropertySource
抽象- 使用
@PropertySource
- 佔位符解析
1.12.1 基本概念:@Bean和@Configuration
Spring的新Java配置支持中的主要構件是@Configuration
註解的類和@Bean
註解的方法。
@Bean
註解使用表示一個方法實例、配置和實例化Spring IoC容器管理的新對象。這些類似Spring的 XML配置,@Bean
註解扮演了元素相同的角色。你可以使用@Bean
註解方法替換任何Spring中@Component
組件。然而,它們最常與@Configuration一起使用。註解類@Configuration
註解表示它的主要目的是bean定義的源。此外,@Configuration
類允許通過調用同一類中的其他@Bean
方法來定義Bean間的依賴關係。下面最簡單的@Configuration
例子:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
前面的AppConfig類是與下面的Spring XML定義相等:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
完整的
@Configuration
與“精簡”@Bean
模式?當
@Bean
方法在類中被聲明時,這些類沒有被註解@Configuration
,它們被稱為以“精簡”模式進行處理。在@Component
或在簡單的舊類中聲明的Bean方法被認為是“精簡版”,其中包含類的主要目的不同,而@Bean
方法在那裡具有某種優勢。例如,服務組件在每個可應用的組件類上通過增加一個附加的@Bean方法暴露管理視圖到容器。在這種場景下,@Bean
方法是一種通用的工廠方法機制。不像完整的
@Configuration
,精簡@Bean
方法不能聲明bean之間的依賴關係。相反,它們對其包含的組件的內部狀態以及可能聲明的參數(可選)進行操作。因此,此類@Bean
方法不應調用其他@Bean
方法。每個這樣的方法實際上只是一個特定bean引用的工廠方法,沒有任何特殊的運行時語義。這裡的積極副作用是在運行時不需要應用CGLIB子類,所以在類設計方面沒有限制(也就是,包含類可能是final)。在常見的場景中,
@Bean
方法是在@Configuration類中聲明的,確保總是使用完整模式,因此交叉方法引用被重定向到容器的生命週期管理。這可以防止通過常規Java調用意外調用相同的@Bean
方法,這有助於減少在lite模式下操作時難以跟蹤的細微錯誤。備註:在@Configuration
類中調用其他@Bean
方法會定向到容器的生命週期管理。
@Bean
和@Configuration
在下面的部分深入討論。首先,我們會覆蓋基於Java註解創建Spring容器的各種方式。
1.12.2 通過使用AnnotationConfigApplicationContext
初始化Spring容器
以下各節介紹了Spring 3.0中引入的Spring的AnnotationConfigApplicationContext
。這個通用的ApplicationContext實現不僅能夠接收@Configuration
類作為輸入,還能夠接收普通的@Component
類和使用JSR-330元數據註解的類
當@Configuration
類作為輸入被提供,@Configuration
類自身作為一個bean定義被註冊並且所有在類中被@Bean
聲明的方法也作為bean的定義被註冊到容器。
當提供@Component
和JSR-330類時,它們被註冊為bean定義,並假設DI元數據(如@Autowired
或@Inject
)在這些類中使用。
簡單構造
與實例化ClassPathXmlApplicationContext
時將Spring XML文件用作輸入的方式幾乎相同,實例化AnnotationConfigApplicationContext
時可以將@Configuration
類用作輸入。如下面的示例所示,這允許完全不使用XML來使用Spring容器:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
像前面提到的,AnnotationConfigApplicationContext
不限於與@Configuration一起使用。任何@Component
或JSR-330註解的類作為輸入構造是被支持,類似下面例子:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
前面的例子假設MyServiceImpl
、Dependency1
、和Dependency2
使用Spring依賴注入註解例如:@Autowired
。
通過使用register(Class<?>…)
編程式的構建容器
你可以通過使用無參構造函數實例化AnnotationConfigApplicationContext
並且通過使用register()
方法配置。當編程地構建AnnotationConfigApplicationContext
時,這個方法是特別地有用。下面類中展示怎樣去使用:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
通過scan(String…)
掃描激活組件
掃描激活組件,你可以註解你的@Configuration
類:
@Configuration
@ComponentScan(basePackages = "com.acme") //1
public class AppConfig {
...
}
- 這個註解激活組件掃描
經驗豐富的Spring用戶可能熟悉Spring context 中等效的XML聲明:命名空間,類似下面例子:
<beans> <context:component-scan base-package="com.acme"/> </beans>
在前面的例子中,com.acm
包被掃描去查找被註解@Component
的類,並且這些類作為Spring bean定義被註冊在容器中。AnnotationConfigApplicationContext
暴露scan(String…)
方法提供相同的組件掃描功能,類似下面的例子:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
記住
@Configuration
類是使用@Component
元註解的,因此它們是組件掃描的候選者。在前面的例子中,假設AppConfig被聲明在com.acme
包中(或任何com.acme
子包),它會在調用scan()
期間被選出來。在refresh()
後,其所有@Bean
方法都將被處理並註冊為容器內的Bean定義。
通過AnnotationConfigWebApplicationContext
支持Web應用程序
AnnotationConfigWebApplicationContext
提供了AnnotationConfigApplicationContext
的WebApplicationContext
變體。當配置Spring的ContextLoaderListener
servlet監聽器、Spring MVC DispatcherServlet
等等的時候,你可以使用這個實現,下面的web.xml
片段配置一個典型的Spring MVC web應用程序(注意:contextClass
使用context-param
和init-param
):
<web-app>
<!--
配置ContextLoaderListener使用AnnotationConfigWebApplicationContext替換默認的XmlWebApplicationContext-->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- 配置路徑必須包含一個或多個以逗號或空格分隔的完全限定的@Configuration類。也可以指定全限定包以進行組件掃描 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 使用ContextLoaderListener引導根應用程序上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 聲明Spring MVC DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置 DispatcherServlet 使用AnnotationConfigWebApplicationContext
替換默認的XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!--同樣,配置路徑必須包含一個或多個逗號或空格分隔且完全限定符的@Configuration類 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 為所有/app/*請求映射到dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
1.12.3 使用@Bean註解
@Bean
是一個方法級別註解並且是XML 元素的直接類比物。這個註解支持一些通過提供的屬性,例如: init-method 、destroy-method 、 autowiring
你可以在@Configuration
和@Component
註解的類上使用@Bean
註解。
聲明Bean
去聲明一個bean,你可以為一個方法註解@Bean
。你使用這個方法在ApplicationContext
中去註冊一個方法返回值指定類型的bean定義。默認情況,bean名稱是相同方法名稱。下面的例子顯示一個@Bean
方法聲明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
前面的配置與下面的Spring XML完全等效:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
這兩者聲明都使一個名為transferService
的bean在ApplicationContext
中可用,並綁定一個TransferServiceImpl
類型的對象實例,類似下面文本圖片顯示:
transferService -> com.acme.TransferServiceImpl
你也可以聲明你的@Bean
方法為一個接口(或者基類)返回類型,類似下面例子展示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
但是,這將高級類型預測的可見性限制為指定的接口類型(TransferService
)。然而,全類型(TransferServiceImpl
)在容器只有一個,受影響的單例bean被初始化。非懶加載單例bean根據它們的聲明順序獲取已經初始化實例,當其他組件嘗試通過非聲明類型去匹配時,你可能看到不同類型匹配結果依賴(例如,@Autowired
TransferServiceImpl
,僅在實例化transferService
bean之後才解析)。
如果你始終通過聲明的服務接口引用你的類型,那麼你的
@Bean
返回類型可以安全地加入該設計決策。但是,對於實現多個接口的組件或由其實現類型潛在引用的組件,聲明最具體的返回類型(至少與引用你的bean的注入點所要求的具體類型一樣)更為安全。 備註:意思是如果通過實現類型引用組件時,在定義@Bean
方法時返回類型要是具體的實現類型。
Bean依賴
一個被@Bean
註解的方法可以有任意個參數,這些參數描述了需要的依賴去構建bean。例如,如果我們的TransferService
需要一個AccountRepository
,我們可以通過方法參數來實現這種依賴關係,類似下面例子:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析機制與基於構造函數的依賴注入幾乎相同。查看更詳細相關部分。
接受生命週期回調
任何被聲明@Bean
註解的類支持普通的生命週期回調並且可以使用 JSR-250的@PostConstruct
和@PreDestroy註解。查看JSR-250註解更多的詳情。
常規的Spring生命週期回調是被完全的支持。如果bean實現InitializingBean
、DisposableBean
或Lifecycle
,它們各自的方法被容器回調。
標準的set*Aware
接口(例如,BeanFactoryAware,、BeanNameAware、MessageSourceAware,、ApplicationContextAware等等)也是完全的被支持。
@Bean
註解支持任意的初始化和銷燬回調方法,非常類似Spring 中XML元素的init-method和destroy-method
屬性,類似下面例子顯示:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
默認情況下,這些bean通過Java配置定義它們有公共的
close
或shutdown
方法自定地加入到銷燬回調中。如果你有一個公共的close
或shutdown
方法並且當容器被關閉時不想被回調,你應該增加@Bean(destroyMethod="")
到你的bean定義中去禁止默認的(inferred
)模式。默認情況下,你可能要對通過JNDI獲取的資源執行此操作,因為其生命週期在應用程序外部進行管理的。特別是,要確保始終為數據源執行此操作,因為在Java EE應用服務器上這是有問題的。
下面的例子展示怎樣去阻止DataSource自動銷燬回調。
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
另外,對於
@Bean
方法,通常使用編程式地JNDI查找,方法是使用Spring的JndiTemplate
或JndiLocatorDelegate
幫助器,或者直接使用JNDIInitialContext
用法,而不使用JndiObjectFactoryBean
變體(這將迫使你將返回類型聲明為FactoryBean
類型,而不是實際的類型。目標類型,因此很難在打算引用此處提供的資源的其他@Bean方法中用於交叉引用調用。
對於前面示例中的BeanOne,它等效於在構造期間直接調用init()
方法,類似下面例子:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
當你直接地工作在Java中,你可以對你的對象做任何事而不需要依賴容器生命週期。
指定Bean作用域
Spring包括@Scope
註解,因此你可以使用bean的作用域。
使用@Scope
註解
你可以指定你的bean定義通過@Bean
註解同時也可以指定一個作用域。你可以使用在Bean作用域部分中任何標準的作用域指定。
默認作用域是singleton
,但是你可以覆蓋這個通過@Scope
註解,類似下面例子顯示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Scope
和scoped-proxy
Spring提供通過作用域代理處理作用域依賴的便捷方式。當使用XML配置< aop:scoped-proxy/>
是最簡單創建一個代理方式。使用@Scope
註解在Java中配置bean,可以通過proxyMode
屬性提供同等的支持。默認是沒有代理(ScopedProxyMode.NO
),但是你可以指定ScopedProxyMode.TARGET_CLASS
或ScopedProxyMode.INTERFACES
如果你使用Java,將XML參考文檔中的作用域代理示例(請參閱作用域代理)移植到我們的@Bean
,它類似於以下內容:
// Http session作用域bean 暴露為代理
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// 引用UserPreferences
service.setUserPreferences(userPreferences());
return service;
}
自定義Bean名稱
默認情況下,配置類使用@Bean
方法的名稱作為bean名稱。這個功能可以被覆蓋,通過@Bean
的name屬性,類似下面例子:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
Bean別名
類似在bean的命名中討論,有時候給一個簡單bean多個名稱,也稱為Bean別名。@Bean
註解的name屬性接受一個字符串數組為這個別名。下面例子展示怎樣去設置bean的別名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Bean描述
有時候,有助於提供有關bean的更詳細的文本描述。當這些bean被暴露為監控目的時,是非常有用的。
去增加一個描述到@Bean
,你可以使用 @Description
註解,類似下面例子展示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
1.12.4 使用@Configuration
註解
@Configuration
是一個類級別註解,它表示對象是bean定義的源。@Configuration
類聲明bean通過公共@Bean
註解方法。在@Configuration
類上調用@Bean
方法能被使用去定義Bean之間依賴關係。查看基礎概念:@Bean和@Configuration
bean間注入的依賴關係
當Bean彼此依賴時,表達這種依賴就像讓一個bean方法調用另一個一樣簡單,類似下面例子展示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
在前面的例子中,beanOne
通過構造函數注入引用beanTwo
。
僅僅當
@Bean
方法被聲明在@Configuration
類中時,這方法聲明bean間的依賴關係有效。你不能通過使用普通的@Component
聲明bean間的依賴關係。
查找方法注入
如前所述,查找方法注入是一個高級特性,它很少地使用。在一些單例作用域bean依賴原型作用域bean場景,它是非常有用的。使用Java為配置類型實現這個模式提供一種自然方法。下面例子展示怎樣去查找方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
通過使用Java配置,在該子類中,你可以創建一個CommandManager
子類,抽象的createCommand()
方法將被覆蓋,以使其查找新的(原型)command對象。通過使用Java配置。下面例子展示怎樣去做:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
有關基於Java的配置在內部如何工作的更多信息
考慮下面例子,它展示了一個@Bean
註解的方法被調用兩次。
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
clientDao(
)在clientService1()
被調用一次並且在clientService2()
也調用一次。因為這個方法創建一個新的ClientDaoImpl
實例並且返回它,你通常希望有兩個實例(每個服務一個)。那肯定是有問題的:在Spring中,默認情況實例化bean作用域是singleton
。這就是神奇之處:所有@Configuration
類在運行時是CGLIB的子類。在子類中,在調用父方法創建實例之前,子類方法首先檢查容器緩存bean。
這種行為根據你的bean作用域可能不同。我們在這裡討論的單例bean。
在Spring3.2以後,不在需要添加CGLIB到你的類路徑下,因為CGLIB類已經被重新打包在
org.springframework.cglib
下並且直接地包含在spring-core
jar中。由於CGLIB在啟動時會動態添加功能,因此存在一些限制。特別地,配置類不能是
final
。然而,在Spring4.3以後,任何構造函數在配置類上是被允許的,包括@Autowired
或沒有默認參數的構造函數申明默認注入的使用。如果你想避免CGLIB的限制,考慮在你非
@Configuration
類(例如,一個普通的@Component
替換)上聲明你的@Bean
方法。在@Bean
方法之間跨方法調用不會被攔截,因此你必在須構造函數或方法級別只依賴需要注入的。代碼示例:
com.liyong.ioccontainer.starter.BaseJavaConfigIocContainer
1.12.5 編寫基於Java的配置
Spring的基於Java配置特性允許你編寫註解,這樣可以降低你的配置複雜性。
使用@Import註解
類似元素被使用在Spring XML文件中去幫助模塊化配置,@Import
註解允許去其他配置類中加載@Bean
定義,類似下面的例子展示:
@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();
}
}
當初始化上下文時,不需要去指定ConfigA.class
和ConfigB.class
,僅僅需要去顯示地提供ConfigB
,類似下面例子展示:
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);
}
這個方式簡化容器的實例化,僅僅一個類需要去處理,而不是在構造期間潛在地記住大量的@Configuration
類。
在Spring4.2以後,
@Import
也支持引用常規的組件類,類似AnnotationConfigApplicationContext.register
方法。如果你想避免組件掃描,這是非常有用地,通過使用一些配置類作為入口點顯示定義所有組件。
在被導入的@Bean
定義中注入依賴
前面的示例有效,但過於簡單。在大多數時間場景中,Bean在配置類之間相互依賴。當使用XML時,這不是問題,因為不涉及編譯器並且你可以聲明 ref="someBean"
並信任Spring在容器初始化期間進行處理。當使用@Configuration
類時,Java編譯器在配置模型上約束,因為對其他bean引用必須是有效的Java語法。
幸好地,解決這個問題是非常簡單的。像我們以前討論過的,@Bean
方法可以有任意數量的參數,這些參數描述了bean的依賴。考慮下面更真實的場景對於這些@Configuration
類,每一個bean在其他類中被定義:
@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");
}
這裡有其他的方式去實現相同的結果。記住@Configuration
類最終只是容器中的另一個bean:這意味著它們可以利用@Autowired
和@Value
注入以及其他與其他bean相同的特性。
確保以這種方式注入的依賴項只是最簡單的一種。
@Configuration
類是在上下文初始化期間非常早地處理的,並且強制以這種方式注入依賴項可能導致意外的早期初始化。如上例所示,儘可能使用基於參數的注入。另外,通過
@Bean
定義BeanPostProcessor
和BeanFactoryPostProcessor時要特別小心。這些應該通常地被聲明為static
@Bean
方法,不要觸發它們包含的配置類初始化。除此之外,@Autowired
和@Value
可能在配置類上不能工作,因為它作為bean實例被創建早於AutowiredAnnotationBeanPostProcessor
。
下面例子展示一個bean怎樣被裝配到其他bean:
@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");
}
在
@Configuration
類中構造方法注入僅支持在Spring4.3以後。注意:如果目標bean定義僅有一個構造函數,那麼不需要去指定@Autowired
。代碼示例:
com.liyong.ioccontainer.starter.BeanAndConfigurationImportContainer
全限定導入bean以更容易導航
在前面的場景中,使用@Autowired
工作很好並且提供期望的模塊化,但是確定自動裝配 bean的定義是在哪裡聲明的仍然有些模稜兩可。例如,作為一個開發者看到ServiceConfig
,怎樣確切的知道@Autowired
AccountRepository
bean在哪裡定義的?它在代碼中不是明確的,這可能很好。記住, Spring Tools 為Eclipse提供可以渲染圖形的工具,這些圖形顯示了所有的對象是怎樣連接的,這可能是你需要的。你的Java IDE能更容易地找到所有聲明和使用AccountRepository
類型並且快速地顯示@Bean
方法的路徑以及返回類型。
如果這種歧義是不可接受的,並且你希望從IDE內部直接從一個@Configuration
類導航到另一個@Configuration
類,請考慮自動裝配配置類本身。下面例子顯示怎樣去做:
@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());
}
}
在前面情況中,AccountRepository
完整的顯示定義。但是,ServiceConfig
現在緊緊的耦合到RepositoryConfig
。這就是需要權衡的。通過使用基於接口或基於抽象類的@Configuration
類,可以在某種程度上緩解這種緊密耦合。考慮下面例子:
@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");
}
現在,ServiceConfig
與具體的DefaultRepositoryConfig
鬆散耦合,並且內建IDE工具仍然非常有用:你可以容易地獲取RepositoryConfig
實現的層級。通過這種方式,導航@Configuration
類及其依賴項與導航基於接口的代碼的通常過程沒有什麼不同。
如果你想流暢啟動創建這些bean的順序,考慮聲明它們為
@Lazy
(用於首次訪問而不是啟動時創建)或像@DependsOn
其他bean(確保其他指定bean在當前bean被創建之前 ,而不受後者直接依賴關係的影響)。
條件地包含@Configuration
類或@Bean
方法
根據某些系統狀態,有條件地啟用或禁用完整的@Configuration
類甚至單個@Bean
方法,通常很有用。說明:可以啟用/禁止@Configuration
類或者@Configuration
類中的@Bean
方法。一個常用的例子是去使用@Profile
註解去激活bean,僅僅當在Spring中的Environment
指定的profile
被激活時(查看Bean定義 Profiles 詳情)。
@Profile
註解通過使用一個非常靈活的註解叫做 @Conditional
實現的。@Conditional
註解指示在註冊@Bean
之前應諮詢特定org.springframework.context.annotation.Condition
實現。
Condition
接口的實現接口提供一個matches(…)
方法,它會返回true或false。例如,下面的清單顯示Condition
對@Profile
真實實現:
@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().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
查看 @Conditional
文檔更多詳細。
結合Java和XML配置
Spring的@Configuration
類不能100%
的完成替換Spring XML。一些工具(如Spring XML namespaces)仍然是配置容器的理想方法。在使用XML方便或有必要的情況下,你可以選擇:使用“ClassPathXmlApplicationContext
以“ XML中心”方式實例化容器,或通過使用AnnotationConfigApplicationContext
和“ Java配置中心”方式實例化容器。 @ImportResource
註解可根據需要導入XML。
以XML為中心的@Configuration
類的使用
從XML引導Spring容器並以特別的方式包含@Configuration
類可能更好。例如,在使用Spring XML的大型現有代碼庫中,根據需要創建@Configuration
類並且現有XML文件中將它們包含在內變得更加容易。在本節的後面,我們將介紹在這種“以XML為中心”的情況下使用@Configuration
類的選項。
- 聲明
@Configuration
類似普通的Spring元素
記住@Configuration
類最終在容器中是bean定義。在這一系列例子中,我們創建@Configuration
類命名為AppConfig
並且包含它在system-test-config.xml中類似定義。因為<context:annotation-config/>
已經打開,容器在AppConfig
中識別@Configuration
註解和處理@Bean方法聲明。
下面例子顯示在Java中普通的配置:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new 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=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在
system-test-config.xml
文件中,AppConfig 沒有聲明id元素。雖然這樣做是可以接受的,但這是不必要的,因為沒有其他bean引用它,而且不太可能通過名稱顯式地從容器中獲取它。同樣,DataSource
bean只能按類型自動裝配,因此也不嚴格要求顯式beanid
。
- 使用
<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
以類為中心的XML與@ImportResource
的結合使用
在應用中,@Configuration
類是配置容器的主要機制,但是仍然可能需要至少使用一些XML。在這個場景中,你可以使用@ImportResource
和定義你需要的XML。這樣做實現了“以Java為中心”的方法來配置容器,並使XML保持在最低限度。下面例子展示了怎樣去使用@ImportResource
註解去實現“以Java為中心”配置並在需要的時候使用XML:
@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);
}
}
properties-config.xml
:
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
代碼示例:
com.liyong.ioccontainer.starter.BaseJavaConfigAndXmlIocContainer
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公眾號:
技術交流群: