目錄
隨著Spring的流行,我們經歷過基於XML-Based 的配置,隨著SpringBoot的流行,我們逐漸使用基於註解的配置替換掉了基於XML-Based的配置,那麼你知道基於註解的配置的基礎元件都是什麼嗎?都包括哪些要素?那麼本節就來探討一下。注:本篇文章更多的是討論Spring基於註解的配置一覽,具體的技術可能沒有那麼深,請各位大佬見諒。
探討主題:
- 基礎概念:@Bean 和 @Configuration
- 使用AnnotationConfigApplicationContext 例項化Spring容器
- 使用@Bean 註解
- 使用@Configuration 註解
- 編寫基於Java的配置
- Bean定義配置檔案
- PropertySource 抽象類
- 使用@PropertySource
- 佔位符的宣告
基礎概念:@Bean 和 @Configuration
Spring中新的概念是支援@Bean註解 和 @Configuration 註解的類。@Bean 註解用來表明一個方法例項化,配置並且通過IOC容器初始化並管理一個新的物件。@Bean註解就等同於XML-Based中的<beans/>
標籤,並且扮演了相同的作用。你可以使用基於註解的配置@Bean 和 @Component,然而他們都用在@Configuration配置類中。
使用@Configuration 註解的主要作用是作為bean定義的類,進一步來說,@Configuration註解的類允許通過呼叫同類中的其他@Bean標註的方法來定義bean之間依賴關係。 如下所示:
新建一個maven專案(我一般都直接建立SpringBoot專案,比較省事),建立AppConfig
,MyService
,MyServiceImpl
類,程式碼如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService(){
return new MyServiceImpl();
}
}
public interface MyService {}
public class MyServiceImpl implements MyService {}
上述的依賴關係等同於XML-Based:
<beans>
<bean id="myService",class="com.spring.annotation.service.impl.MyServiceImpl"/>
</beans>
使用AnnotationConfigApplicationContext 例項化Spring容器
AnnotationConfigApplicationContext 基於註解的上下文是Spring3.0 新新增的註解,它是ApplicationContext
的一個具體實現,它可以接收@Configuration
註解的類作為輸入引數,還能接收使用JSR-330元註解的普通@Component類。
當提供了@Configuration 類作為輸入引數時,@Configuration類就會註冊作為bean的定義資訊並且所有宣告@Bean的方法也都會作為bean的定義資訊。
當提供@Component和JSR-330 宣告的類時,他們都會註冊作為bean的定義資訊,並且假設在必要時在這些類中使用諸如@Autowired或@Inject之類的註解
簡單的構造
在某些基於XML-Based的配置,我們想獲取上下文容器使用ClassPathXmlApplicationContext
,現在你能夠使用@Configuration 類來例項化AnnotationConfigApplicationContext。
在MyService
中新增一個printMessage()
方法,實現類實現對應的方法。新建測試類進行測試
public class ApplicationTests {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
// printMessage() 輸出something...
service.printMessage();
}
}
如前所述,AnnotationConfigApplicationContext不僅限於使用@Configuration類。 任何@Component或JSR-330帶註釋的類都可以作為輸入提供給建構函式,如下例所示
public class ApplicationTests {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyServiceImpl.class,Dependency1.class,Dependency2.class);
MyService myService = context.getBean(MyService.class);
myService.printMessage();
}
}
使用register註冊IOC容器
你可以例項化AnnotationConfigApplicationContext
通過使用無引數的構造器並且使用register
方法進行註冊,它和AnnotationConfigApplicationContext
帶引數的構造器起到的效果相同。
public class ApplicationTests {
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);
System.out.println(ctx.getBean(OtherConfig.class));
System.out.println(ctx.getBean(AdditionalConfig.class));
myService.printMessage();
}
}
OtherConfig.class 和 AdditionalConfig.class 是使用@Component 標註的類。
允許scan()方法進行元件掃描
為了允許元件進行掃描,需要在@Configuration配置類新增@ComponentScan()
註解,改造之前的AdditionalConfig
類,如下:
@Configuration
@ComponentScan(basePackages = "com.spring.annotation.config")
public class AdditionalConfig {}
@ComponentScan指定了基礎掃描包位於com.spring.annotation.config下,所有位於該包範圍內的bean都會被註冊進來,交由Spring管理。它就等同於基於XML-Based的註解:
<beans>
<context:component-scan base-package="com.spring.annotation.config/>
</beans>
AnnotationConfigApplicationContext中的scan()方法以允許相同的元件掃描功能,如以下示例所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.spring.annotation");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
為什麼說@Configuration用法和@Component都能夠標註配置類?因為@Configuration的元註解就是@Component。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { String value() default ""; }
使用AnnotationConfigWebApplicationContext支援web容器
AnnotationConfigApplicationContext的一個WebApplicationContext的變化是使用AnnotationConfigWebApplicationContext
。配置Spring ContextLoaderListener的servlet監聽器,Spring MVC的DispatcherServlet等時,可以使用此實現。以下web.xml程式碼段配置典型的Spring MVC Web應用程式(請注意context-param和init-param的使用)
<web-app>
<!-- 配置web上下文監聽器使用 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.spring.annotation.config.AdditionalConfig</param-value>
</context-param>
<!--使用ContextLoaderListener像往常一樣引導根應用程式上下文-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 定義一個SpringMVC 核心控制器 DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置web上下文監聽器使用 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.spring.annotation.config.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 將/app/* 的所有請求對映到排程程式servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
使用@Bean註解
@Bean 註解是一個方法級別的註解,能夠替換XML-Based中的init-method
, destroy-method
, autowiring
。
定義一個Bean
與基礎概念中Bean的定義相同,讀者可以參考基礎概念部分進行了解,我們不在此再進行探討。
Bean的依賴
@Bean 註解可以有任意數量的引數來構建其依賴項,例如
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public String generateSomeString() {
return myRepository.findString() + "-from-MyService";
}
}
@Configuration
class MyConfiguration {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
public class MyRepository {
public String findString() {
return "some-string";
}
}
接受生命週期回撥
任何使用@Bean的註解都支援生命週期的回撥,使用JSR-220提供的@PostConstruct
和@PreDestory
註解來實現。如果bean實現了InitializingBean
,DisposableBean
或者Lifecycle
介面,他們的方法會由IOC容器回撥。一些以Aware的實現介面(像是BeanFactoryAware,BeanNameAware, MessageSourceAware, ApplicationContextAware等)也支援回撥。
@Bean註解支援特定的初始化和銷燬方法,就像XML-Based中的init-method
和 destory-method
中的bean屬性,下面這個例子證實了這一點
AppConfig.java
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne(){
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo(){
return new BeanTwo();
}
}
class BeanOne {
public void init(){}
}
class BeanTwo {
public void cleanup(){}
}
對於上面的例子,也可以手動呼叫init()方法,與上面的initMethod 方法等效
@Bean
public BeanOne beanOne(){
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
當你直接使用Java開發時,你可以使用物件執行任何操作,並且不必總是依賴於容器生命週期。
Bean的作用範圍
Spring包括@Scope註解能夠讓你指定Bean的作用範圍,Bean的Scope預設是單例的,也就是說@Bean標註的物件在IOC的容器中只有一個。你可以重寫@Scope的作用範圍,下面的例子說明了這一點,修改OtherConfig如下
OtherConfig.java
@Configuration
public class OtherConfig {
@Bean
@Scope("prototype")
public Dependency1 dependency1(){
return new Dependency1();
}
}
每次嘗試獲取dependency1這個物件的時候都會重新生成一個新的物件例項。下面是Scope的作用範圍和解釋:
Scope | Descriptionn |
---|---|
singleton | 預設單例的bean定義資訊,對於每個IOC容器來說都是單例物件 |
prototype | bean物件的定義為任意數量的物件例項 |
request | bean物件的定義為一次HTTP請求的生命週期,也就是說,每個HTTP請求都有自己的bean例項,它是在單個bean定義的後面建立的。僅僅在web-aware的上下文中有效 |
session | bean物件的定義為一次HTTP會話的生命週期。僅僅在web-aware的上下文中有效 |
application | bean物件的定義範圍在ServletContext生命週期內。僅僅在web-aware的上下文中有效 |
websocket | bean物件的定義為WebSocket的生命週期內。僅僅在web-aware的上下文中有效 |
@Scope和Scoped-proxy
Spring提供了一種通過scoped proxies與scoped依賴一起作用的方式。最簡單的在XML環境中建立代理的方式是通過<aop:scoped-proxy/>
標籤。使用@Scope
註解為在Java中配置bean提供了與proxyMode屬性相同的功能。預設是不需要代理的(ScopedProxyMode.NO),但是你需要指定ScopedProxyMode.TARGET_CLASS
或者ScopedProxyMode.INTERFACES
。
自定義Bean名稱
預設的情況下,配置類通過@Bean配置的預設名稱(方法名第一個字母小寫)進行註冊和使用,但是你可以更換@Bean的name為你想指定的名稱。修改AdditionalConfig 類
AdditionalConfig.java
@Configuration
//@ComponentScan(basePackages = "com.spring.annotation.config")
public class AdditionalConfig {
@Bean(name = "default")
public Dependency2 dependency2(){
return new Dependency2();
}
}
Bean的別名
有時候需要為單例的bean提供多個名稱,也叫做Bean的別名。Bean註解的name屬性接收一個Array陣列。下面這個例子證實了這一點:
OtherConfig.java
@Configuration
public class OtherConfig {
// @Bean
// @Scope("prototype")
// public Dependency1 dependency1(){
// return new Dependency1();
// }
@Bean({"dataSource", "dataSourceA", "dataSourceB"})
public DataSource dataSource(){
return null;
}
}
Bean的描述
有時,提供更詳細的bean描述資訊會很有幫助(但是開發很少使用到)。為了增加一個對@Bean的描述,你需要使用到@Description註解
OtherConfig.java
@Configuration
public class OtherConfig {
// @Bean
// @Scope("prototype")
// public Dependency1 dependency1(){
// return new Dependency1();
// }
// @Bean({"dataSource", "dataSourceA", "dataSourceB"})
// public DataSource dataSource(){
// return null;
// }
@Bean
@Description("此方法的bean名稱為dependency1")
public Dependency1 dependency1(){
return new Dependency1();
}
}
使用@Configuration註解
更多關於@Configuration 的詳細說明,請你參考https://mp.weixin.qq.com/s/FLJTsT2bAru-w7cF4CG8kQ
已經把@Configuration的註解說明的比較詳細了。
組成Java-Based環境配置的條件
Spring基於註解的配置能夠允許你自定義註解,同時能夠降低配置的複雜性。
使用@Import註解
就像在Spring XML檔案中使用
@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);
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
這種方法簡化了容器例項化,因為只需要處理一個類,而不是要求你在構造期間記住可能大量的@Configuration類
有選擇性的包含@Configuration 類和@Bean 方法
選擇性的允許或者禁止@Configuration註解的類和@Bean註解的方法是很有用的,基於一些任意系統狀態。一個常見的例子是隻有在Spring環境中啟用了特定的配置檔案時才使用@Profile註釋啟用bean。
@Profile註解也實現了更靈活的註解@Conditional,@Conditional 註解表明在註冊@Bean 之前應參考特定的Condition實現。
實現Condition介面就會提供一個matched方法返回true或者false
更多關於@Conditional 的示例,請參考
https://www.cnblogs.com/cxuanBlog/p/10960575.html
結合Java與XML配置
Spring @Configuration類能夠100%替換XML配置,但一些工具(如XML名稱空間)仍舊是配置容器的首選方法,在這種背景下,使用XML使很方便的而且使剛需了。你有兩個選擇:使用以XML配置例項化容器為中心,例如:ClassPathXmlApplicationContext
匯入XML或者例項化以Java配置為中心的AnnotationConfigApplicationContext
並提供ImportResource
註解匯入需要的XML配置。
將@Configuration宣告為普通的bean元素
請記住,@Configuration類存放的是容器中的bean定義資訊,下面的例子中,我們將會建立一個@Configuration類並且載入了外部xml配置。下面展示了一個普通的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>
<!--允許開啟 @Autowired 或者 @Configuration-->
<context:annotation-config/>
<!-- 讀取外部屬性檔案 -->
<!-- 更多關於屬性讀取的資料,參考 https://www.cnblogs.com/cxuanBlog/p/10927819.html -->
<context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>
<bean class="com.spring.annotation.config.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
引入jdbc.properties建立資料庫連線
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sys
jdbc.username=root
jdbc.password=123456
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/spring/annotation/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
在
system-test-config.xml
中,AppConfig 對應的標籤沒有宣告id屬性,雖然這樣做是可以接受的,但是沒有必要,因為沒有其他bean引用它,並且不太可能通過名稱從容器中獲取它。同樣的,DataSource bean只是按型別自動裝配,因此不嚴格要求顯式的bean id。
使用< > 挑選指定的@Configuration類
因為@Configuration的原註解是@Component,所以@Configuration註解的類也能用於元件掃描,使用與前一個示例中描述的相同的方案,我們可以重新定義system-test-config.xml以利用元件掃描。 請注意,在這種情況下,我們不需要顯式宣告<context:annotation-config />
,因為<context:component-scan />
啟用相同的功能。
<beans>
<context:component-scan base-package="com.spring.annotation"/>
<context:property-placeholder location="classpath:/com/spring/annotation/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration 類使用@ImportResource
在基於Java註解的配置類中,仍然可以使用少量的@ImportResource匯入外部配置,最好的方式就是兩者結合,下面展示了一下Java註解結合XML配置的示例
@Configuration
@ImportResource("classpath:/com/spring/annotation/properties-config.xml")
public class AppConfig {
@Value("${jdbc.driverClassName}")
private String driver;
@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/spring/annotation/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sys
jdbc.username=root
jdbc.password=123456
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
相關閱讀:
PropertyPlaceholderConfigurer 基本用法
談談 ServletConfig 和 ServletContext
@Configuration全部配置一覽https://mp.weixin.qq.com/s/FLJTsT2bAru-w7cF4CG8kQ