Spring基於Annotation的依賴注入小結

  最近接觸了幾個比較大的Java項目,其中經常存在Bean之間的依賴,例如在某服務啓動前要初始化各種詞典,主要方式有顯式聲明Bean和隱式註解注入2種,本文着重介紹後一種。java

1. 依賴注入簡介

  首先咱們定義「依賴」是什麼:若是在Class A中有Class B的實例,則稱A依賴B。
如今咱們來構造一個依賴:數據庫

public class UserDao {
    DBConnection conn;
    
    public UserDao() {
        // 先初始化數據庫鏈接
        conn = new DBConnection();
    }
}

上述代碼中,UserDao即對DBConnection存在依賴。但這樣構造依賴存在以下問題:
(1) 耦合度高。設想在將來咱們爲DBConnection的構造函數新增字段name,那麼上述代碼將修改成:
conn = new DBConnection();
conn = new DBConnection(String name);
(2) 若是DBConnection的初始化耗時很長,將影響構造函數的後續初始化工做。express

上述UserDao在構造函數裏直接初始化DBConnection的過程,屬於主動初始化依賴對象,兩者耦合度高,不方便測試。若是被依賴對象事先就初始化好了,直接供給本Class調用,則稱之爲依賴注入。例如上述代碼可改寫爲以下:app

public class UserDao {
    @Autowired  //依賴注入
    DBConnection conn;
    public UserDao() {
    }
}

要使注入生效,咱們還須要在DBConnection中添加Bean標識Component函數

@Component //將類標識爲Bean
public class DBConnection {
    public DBConnection() {
        init(); //初始化工做
    }
}

同時,爲了讓Bean的標識被探測到,咱們還須要在xml配置文件中添加自動掃描:測試

<context:component-scan base-package="org.example.service"/>

到此處相信讀者大體瞭解其注入原理:code

  1. 將一些初始化詞典、數據庫鏈接的類添加諸如@Component的標識,使之成爲Bean。
  2. Spring作自動掃描記錄下這些Bean。
  3. 調用方使用@Autowired對Bean進行依賴注入。
    此處我將這3步分別命名爲:服務Bean化、自動掃描、依賴注入。接下來我將詳細介紹這3部分。

2. 服務Bean化

  事實上咱們能夠在任何狀況下使用@Component標識須要被"Bean化"的服務。但不久咱們會發現,其餘更有水平的代碼裏會有更多富於變化的標識,例如:@Repository@Service@Controller。這是怎麼回事?設想咱們如今有加載本地詞典的類A,本地詞典監控類B,兩者有明顯區別:前者的功能視數據訪問,後者則是監控服務,咱們更願意對標識作更精細化的區分,具體以下:
  @Repository是爲DAO(數據訪問)特製的聲明,將一個類聲明爲Repository意味着該類主要功能是數據讀取、DB訪問。
  @Service則聲明該類爲服務性質的Bean,例如上文中的詞典監控服務,還有許多無關數據操做的功能性初始化類,都應該被標識爲Service
  @Controller聲明標誌着一個類是SpringWeb的MVC控制器,主要負責MVC之間的操縱。另外一個相似的聲明是@RequestMapping,將URL映射爲一個方法。component

以上3個新的聲明均爲@Component的子集,所以僅僅使用@Component也不影響後續的掃描和注入過程。但在現實生活中,其實只有不多的場合咱們須要用到@Component,例如沒法給類作清晰的定位時。xml

3. 註解掃描

<context:component-scan base-package="org.example.service"/>

注意到上文中的這段自動掃描,它的包名其實能夠根據用戶需求作出更改。例如:如今我有一個判斷query是否爲髒詞的服務A,它依賴注入了自動加載髒詞詞典的類B。此外,項目中還存在訪問數據庫的類C,但咱們沒有爲數據庫配置訪問信息。當咱們將註解掃描範圍設置爲整個項目時,啓動服務A將產生問題。對象

解決方案是縮小自動掃描範圍,使得Spring僅掃描B而不掃描C。具體作法是增長掃描過濾器, Spring支持正則Regex和AspectJ兩種表達式的方式進行掃描過濾。
例如:

<context:component-scan base-package="org.example.service">
    <context:include-filter type="regex" 
        expression="org\.example\.service\.dictionary\..*"/>
    <context:exclude-filter type="aspectj" 
        expression="org.example.service.db..*"/>
</context:component-scan>

上面的xml配置就成功的掃描名爲com.example.service.dictionary的package而忽略了以org.example.service.db爲前綴的package。

4. 依賴注入

@Autowired:負責自動裝配被成功掃描的Bean。
@Qualifier("xxxxx"): 有時候咱們有參數不一樣的兩個相同類的bean須要裝配(具體爲何會存在這種現象?可想象要鏈接2個數據庫。),那麼如何單獨指定我須要裝配哪個呢?@Qualifier配合@Autowired可用於消除這類歧義。

在方法內部,咱們也有一些"註解":
@PostConstruct@PreDestroy是Bean內分別執行初始化和銷燬bean的註解。b>注意!它並不屬於Spring,而是屬於J2ee裏的jar包,所以使用時須要在xml配置:

<context:annotation-config />

@PostConstruct: 初始化bean
@preDestroy: 結束前清理。

相關文章
相關標籤/搜索