1.使用注解定義Bean
前面說過,不管是 XML 還是注解,它們都是表達 Bean 定義的載體,其實質都是為 Spring 容器提供 Bean 定義的信息,在表現形式上都是將 XML 定義的內容通過類注解進行描述。Spring 從2.0開始就引入了基於注解的配置方式,在2.5時得到了完善,在4.0時進一步增強。
我們知道,Spring 容器成功啟動的三大要件分別是 Bean定義信息、Bean實現類及 Spring 本身。如果采用基於XML 的配置,則 Bean定義信息和 Bean實現類本身是分離的:而如果采用基於注解的配置文件,則 Bean定義信息通過在 Bean實現類上標注注解實現。
下面是使用注解定義一個 DAO 的 Bean:
import org.springframework.stereotype.Component; //①通過 Repository 定義一個 DAO 的 Bean @Component("userDao") public class UserDao { ... }
在①處使用 @Component 注解在 UserDao 類聲明處對類進行標注,它可以被 Spring 容器識別,Spring 容器自動將 POJO 轉換為容器管理的 Bean。它和以下 XML 配置是等效的:
<bean id="userDao" class="com.smart.anno.UserDao"/>
除 @Component 外,Spring還提供了3個功能基本和 @Component 等效的注解,分別用於對 DAO、Service及 Web 層的 Controller 進行注解。
@Repository:用於對 DAO 實現類進行標注。
@Service:用於對 Service 實現類進行標注。
@Controller:用於對 Controller 實現類進行標注。
之所以要在 @Component 之外提供這3個特殊的注解,是為了讓標注類本身的用途清晰化,完全可以用 @Component 替代這3個特殊的注解。但是,我們推薦使用特定的注解標注特定的 Bean,畢竟這樣一眼就可以看出 Bean 的真實身份。
2.掃描注解定義的Bean
Spring 提供了一個 context 命名空間,它提供了通過掃描類包以應用注解定義 Bean 的方式,如下所示。
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.smart.anno"/> </beans>
通過 context 命名空間的 component-scan 的 base-package 屬性指定一個需要掃描的基類包,Spring 容器將會掃描這個基類包里的所有類,並從類的注解信息中獲取 Bean 的定義信息。
如果僅希望掃描特定的類而非基包下的所有類,那么可以使用 resource-pattern 屬性過濾出特定的類,如下:
<context:component-scan base-package="com.smart" resource-pattern="anno/*.class" />
這里將基類包設置為 com.smart;默認情況下 resource-pattern 屬性的值為“**/*.class”,即基類包里的所有類,將其設置為"anno/*.class",則 Spring 僅會掃描基類包里 anno 子包中的類。
通過 resource-pattern 屬性可以按資源名稱對基類包中的類進行過濾。如果只使用 resource-pattern,就會發現很多時候它並不能滿足要求,如僅需過濾基類包中實現了 XxxSemce 接口的類或標注了某個特定注解的類等。
不過這些需求可以很容易地通過 <context:component-scan> 的過濾子元素實現,如下:
<context:component-scan base-package="com.smart"> <context:include-filter type="regex" expression="com\.smart\.anno.*Dao"/> <context:include-filter type="regex" expression="com\.smart\.anno.*Service"/> <context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/> </context:component-scan>
<context:include-filter> 表示要包含的目標類,而 <context:exclude-filter> 表示要排除的目標類。
一個 <context:component-scan> 下可以擁有若干個 <context:exclude-filter> 和 <context:include-filter> 元素。這兩個過濾元素均支持多種類型的過濾表達式,說明如下。
類型 | 示例 | 說明 |
annotation | com.smart.XxxAnnotation | 所有標注了XxxAnnotation的類。該類型采用目標類是否標注了某個注 |
assignable | com.smart.XxxService | 所有繼承或擴展XxxService的類。該類型采用目標類是否繼承或擴展了某個特定類進行過濾 |
aspectj | com.smart.*Service+ | 所有類名以Service結束的類及繼承或擴展它們的類 |
regex | com\.smart\.anno\..* | 所有com.smart.anno類包下的類。該類型采用正則表達式根據目標類的類名進行過濾 |
custom | com.smart.XxxTypeFilter | 采用XxxTypeFilter代碼方式實現過濾規則。該類必須實現org.springframework.core.type.TypeFilter接口 |
在所有這些過濾類型中,除 custom 類型外,aspectj 的過濾表達能力是最強的,它可以輕易實現其他類型所能表達的過濾規則。
<context:component-scan/> 擁有一個容易被忽視的 use-default-filters 屬性,其默認值為 true,表示默認會對標注 @Component、@Controller、@Service 及 @Reposity 的 Bean 進行掃描。<context:component-scan/> 先根據 <exclude-filter/> 列出需要排除的黑名單,再通過 <include-filter/> 列出需要包含的白名單。由於 use-default-filters 屬性默認值的作用,下面的配置片段不但會掃描 @Controller 的Bean,還會掃描 @Component、@Service 及 @Reposity 的 Bean。
<context:component—scan base—package="com.smart"> <context:include—filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component—scan>
換言之,在以上配置中,加不加 <context:include-filter/> 的效果都是一樣的。如果想僅掃描 @Controller 的Bean,則必須將 use-default-filters 屬性設置為 false。
<context:component—scan base—package="com.smart" use-default-filters="false"> <context:include—filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component—scan>
3.自動裝配Bean
1)使用 @Autowired 進行自動注入
Spring 通過 @Autowired 注解實現 Bean 的依賴注入。來看一個 LogonService 的例子,如下所示。
//①定義一個 Service 的 Bean @Service public class LogonService implements BeanNameAware { //②分別注入logDao和userDao的Bean @Autowired private LogDao logDao; @Autowired private UserDao userDao; }
在①處使用 @Service 將 LogonService 標注為一個 Bean,在②處通過 @Autowired 注入 LogDao 及 UserDao 的 Bean。@Autowired 默認按類型(byType)匹配的方式在容器中查找匹配的 Bean,當有且僅有一個匹配的 Bean 時,Spring 將其注入 @Autowired 標注的變量中。
2)使用 @Autowired 的 required 屬性
如果容器中沒有一個和標注變量類型匹配的 Bean,那么 Spring 容器啟動時將報 NoSuchBeanDefinitionException 異常。如果希望 Spring 即使找不到匹配的 Bean 完成注入也不要拋出異常,那么可以使用 @Autowired(required=false) 進行標注,如下所示。
... @Service public class LogonService implements BeanNameAware { @Autowired(required=false) private LogDao logDao; ... }
在默認情況下,@Autowired 的 required 屬性值為 ture,即要求必須找到匹配的 Bean,否則將報異常。
3)使用 @Qualifier 指定注入 Bean 的名稱
如果容器中有一個以上匹配的 Bean時,則可以通過 @Qualifier 注解限定 Bean 的名稱,如下所示。
@Service public class LogonService implements BeanNameAware { @Autowired private LogDao logDao; //① @Autowired @Qualifier("userDao") private UserDao userDao; }
這時,假設容器有兩個類型為 UserDao 的 Bean,一個名為 userDao,另一個名為 otherUserDao,則①處會注入名為 userDao 的 Bean。
4)對類方法進行標注
@Autowired 可以對類成員變量及方法的入參進行標注,下面在類的方法上使用 @Autowired 注解,如下所示。
@Service public class LogonService implements BeanNameAware { private LogDao logDao; private UserDao userDao; //①自動將LogDao傳給方法入參 @Autowired public void setLogDao(LogDao logDao) { this.logDao = logDao; } //②自動將名為userDao的Bean傳給入參 @Autowired @Qualifier("userDao") public void setUserDao(UserDao userDao) { System.out.println("auto inject"); this.userDao = userDao; } }
如果一個方法擁有多個入參,則在默認情況下,Spring 將自動選擇匹配入參類型的 Bean 進行注入。Spring 允許對方法入參標注 @Qualifier 以指定注入 Bean 的名稱,如下:
@Autowired public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){ System.out.println("multi param inject"); this.userDao = userDao; this.logDao =logDao; }
在以上例子中,UserDao 的入參注入名為 userDao 的 Bean,而 LogDao 的入參注入 LogDao 類型的Bean。
一般情況下,在 Spring 容器中大部分 Bean 都是單實例的,所以一般無須通過 @Repository、@Service 等注解的 value 屬性為 Bean 指定名稱,也無須使用 @Qualifier 注解按名稱進行注入。
雖然 Spring 支持在屬性和方法上標注自動注入注解 @Autowlred,但在實際項目開發中建議采用在方法上標注@Autowired 注解,因為這樣更加“面向對象”,也方便單元測試的編寫。如果將注解標注在私有屬性上,則在單元測試時就很難用編程的辦法設置屬性值。
5)對集合類進行標注
如果對類中集合類的變量或方法入參進行 @Autowired 標注,那么 Spring 會將容器中類型匹配的所有 Bean 都自動注入進來。下面來看一個具體的例子,如下所示。
@Component public class MyComponent { //①Spring 會將容器中所有類型為Plugin的Bean注入這個變量中 @Autowired(required=false) private List<Plugin> plugins; //②將Plugin類型的Bean注入Map中 @Autowired private Map<String,Plugin> pluginMaps; public List<Plugin> getPlugins() { return plugins; } }
Spring 如果發現變量是一個 List 和一個 Map 集合類,則它會將容器中匹配集合元素類型的所有 Bean 都注入進來。在②處將實現 Plugin 接口的 Bean 注入 Map 集合,是 Spring4.0 提供的新特性,其中 key 是 Bean 的名字,value 是所有實現了 Plugin 的 Bean。
這里,Plugin 是一個接口,它擁有兩個實現類,分別是 OnePlugin 和 TwoPlugin,其中 OnePlugin 如下所示。
@Component @Order(value = 1)//①指定此插件的加載順序,值越小,優先被加載 public class OnePlugin implements Plugin{ }
通過 @Component 標注為 Bean,Spring 會將 OnePlugin 和 TwoPlugin 這兩個 Bean都注入 plugins 中。在默認情況下,這兩個 Bean 的加載順序是不確定的,在Spring4.0中可以通過 @Order 注解或實現 Ordered 接口來決定Bean加載的順序,值越小,優先被加載。
6)對延遲依賴注入的支持
Spring4.0 支持延遲依賴注入,即在 Spring 容器啟動的時候,對於在 Bean 上標注 @Lazy 及 @Autowired 注解的屬性,不會立即注入屬性值,而是延遲到調用此屬性的時候才會注入屬性值,如下所示。
@Lazy//①此處需要標注延遲注解 @Repository public class LogDao implements InitializingBean { } @Service public class LogonService implements BeanNameAware { @Lazy//②此處需要標注延遲注解 @Autowired(required=false) private LogDao logDao; }
對 Bean 實施延遲依賴注入,要注意 @Lazy 注解必須同時標注在屬性及目標Bean上,如示例的①和②處,二者缺一,則延遲注入無效。
7)對標准注解的支持
此外,Spring 還支持 JSR-250 中定義的 @Resource 和 JSR-330 中定義的 @lnject 注解,這兩個標准注解和 @Autowired 注解的功用類似,都是對類變更及方法入參提供自動注入功能。@Resource 注解要求提供一個 Bean 名稱的屬性,如果屬性為空,則自動采用標注處的變量名或方法名作為 Bean 的名稱,如下所示。
@Component public class Boss { private Car car; @Resource("car") private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } }
這時,如果 @Resource 未指定 "car” 屬性,則也可以根據屬性方法得到需要注入的 Bean 名稱。可見 @Autowired 默認按類型匹配注入 Bean,@Resource 則按名稱匹配注入Bean。而 @lnject 和 @Autowired 同樣也是按類型匹配注入Bean的,只不過它沒有 required 屬性。可見,不管是 @Resource 還是 @lnject 注解,其功能都沒有 @Autowired 豐富,因此,除非必要,大可不必在乎這兩個注解。
4.Bean作用范圍及生命過程方法
通過注解配置的 Bean 和通過 <bean> 配置的 Bean 一樣,默認的作用范圍都是 singleton。Spring 為注解配置提供了一個 @Scope 注解,可以通過它顯式指定 Bean 的作用范圍,如下所示。
@Scope(BeanDefinition.SCOPE_PROTOTYPE) @Component public class Car { ... }
在使用 <bean> 進行配置時,可以通過 init-method 和 destory-method 屬性指定 Bean 的初始化及容器銷毀前執行的方法。Spring 從2.5開始支持 JSR-250 中定義的 @PostConstruct 和 @PreDestroy 注解,在 Spring 中它們相當於 init-method 和 destory-method 屬性的功能,不過在使用注解時,可以在一個 Bean 中定義多個 @PostConstruct 和 @PreDestroy 方法,如下所示。
@Component public class Boss { private Car car; public Boss(){ System.out.println("construct..."); } @Autowired private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } @PostConstruct private void init1(){ System.out.println("execute in init1"); } @PostConstruct private void init2(){ System.out.println("execute in init2"); } @PreDestroy private void destory1(){ System.out.println("execute in destory1"); } @PreDestroy private void destory2(){ System.out.println("execute in destory2"); } }
運行如下代碼啟動和關閉容器
public class SimpleTest { public static void main(String[] args) throws Throwable { //①啟動容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/anno/beans.xml"); //②關閉容器 ((ClassPathXmlApplicationContext)ctx).destroy(); } }
運行結果:
construct...
execute in setCar
execute in init1
execute in init2
execute in destory1
execute in destory2
這說明 Spring 先調用 Boss 的構造函數實例化 Bean,再執行 @Autowired 進行自動注入,然后分別執行標注了@PostConstruct 的方法,在容器關閉時,則分別執行標注了 @PreDestroy 的方法。