Spring Bean 的裝配方式
Spring Bean 的裝配方式
裝配 Bean 的三種方式
一個程式中,許多功能模組都是由多個為了實現相同業務而相互協作的元件構成的。而程式碼之間的相互聯絡又勢必會帶來耦合。耦合是個具有兩面性的概念,高度的耦合會導致程式碼難以複用,難以測試,難以理解;但同時耦合又是必須的,不耦合的程式碼什麼也做不了。
在 Spring
中,容器負責了把需要相互協作的物件引用賦予各個物件,物件無需自己查詢或建立與其相關聯的物件。而建立應用物件之間的協作稱之為裝配(wiring
),也就是依賴注入(DI
)的本質。
在 Spring
中裝配 Bean
有以下三種常見的方式:
- 在 XML 中進行顯示配置;
- 在 Java 程式碼中顯示配置;
- 隱式的 Bean 發現機制和自動裝配;
自動化裝配 Bean
三種裝配方式中,最常用最高效的就是自動化裝配了,Spring 從兩個角度來實現自動化裝配:
- 元件掃描(
component scanning
):Spring 會自動發現應用上下文中所建立的 Bean。 - 自動裝配(
autowiring
):Spring 自動滿足 Bean 之間的依賴。
以我們平時程式碼中的三層架構舉例,首先建立一個 Dao 層的介面類和實現類
介面類
@Repository public interface UserDao { /** * 根據 Username 和 password 查詢 User 物件 * @param usernmae 賬號名稱 * @param password 賬戶密碼 * @return User */ public User getUser(String Username, String password); }
實現類
public class UserDaoImpl implements UserDao {
public User getUser(String email, String password) {
// do something...
return user;
}
}
然後當我們在 Service 層需要呼叫到上面 Dao 層的例項化物件時,我們只需要簡單宣告一個對應的類,並上標註解即可
public class UserServiceImpl implements UserService { @Autowired UserDao userDao; @Override public User login(String email, String password) { return userDao.getUser(email, password); } }
之後,你還需要在配置檔案中啟動元件掃描。在 spring-context.xml
檔案中(或者這個檔案在你專案中叫其他名字,無所謂)配置 <context:component-scen>
元素。
<context:component-scan base-package="yourpackage" />
當然,這裡你也不一定只能通過 xml 檔案形式來配置,也可以通過基於 Java 的配置。
@Configuration
@CompomentScan(basePackages = "yourpackage")
public class BeanConfig() {}
現在這段程式碼應該就能發揮它的實際用途了。
回顧剛剛的程式碼,可以發現有 @Repository
、 @Autowired
這兩個註解。當你宣告一個 Bean 的時候,你需要在類上使用相應的註解來讓 Spring 的上下文找到這個類,類似於 Repository
, Spring 中還提供了 3 個功能基本等效的註解:
@Repository
:用於對 DAO 的實現類進行註解;@Service
:用於對 Service 的實現類進行註解;@Controller
:用於對 Controller 的實現類進行註解;@Component
:用於註解一些工具類或其他不歸與上述類別的元件。
而當你需要引入一個 Bean 時,@AutoWired
就可以將 Bean 注入到程式碼中。
不過 @AutoWired
註解不僅能夠用在構造器上,還能用在屬性的 Setter
方法上,如下面程式碼所示
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
不過實際上 Setter 方法沒有任何特殊之處,@Autowried
註解可以用在類的任何方法上。
但是不管是構造器,Setter 方法還是其他的方法,Spring 都會嘗試滿足方法引數上所宣告的依賴。假如有且只有一個 Bean 匹配依賴需求的話,那麼這個 Bean 將會被裝配進來。
如果沒有匹配的 Bean,那麼在應用上下文建立的時候,Spring 就會丟擲異常,為了避免異常的出現,你可以將 @Autowired
的 required
屬性設定為 false
。
@Autowired(required = false)
UserDao userDao;
將 required
屬性設定為 false
時, Spring 依舊會嘗試進行自動裝配,但是如果沒有匹配到 Bean 的話,Spring 會將這個處於未裝配狀態。不過我們在實際開發中,畢竟我們不大可能引入一個後面程式碼沒有引用到的物件,而這可能讓你的程式報空指標異常。
@Auowried
註解是通過 byType
方式注入的,如果環境中不止匹配到兩個 Bean 時,或許你該給你的 Bean 進行命名,然後在 @Qualifier
的屬性中指向該 Bean 的名稱。
@Component("beanName")
public class Bean() {
// write something
}
@Autowried
@Qualifier("beanName")
Bean bean;
通過 Java 程式碼配置 Bean
通過 Java 程式碼配置 Bean 的方式在理解起來要簡單明瞭許多,而且這種方式也有其特定的應用場景,比如說你要將第三方庫中的元件裝配到你的應用中,在這種情況下,是沒有辦法在它的類上新增 @Component
或者其他註解的,因此就不能使用自動化裝配的方案了。所以在這種情況下,顯示裝配就成了新的選擇,下面我們先來了解下如何通過 Java 程式碼配置 Bean。
建立配置類
@Configuration
public class BeanConfig() {
}
@Configuration
註解表明這個類是一個配置類,之後我們將在該類中配置 Spring 應用上下文中如何建立類的細節。
宣告 Bean
然後在配置類中宣告要配置 Bean,我們需要編寫一個方法,這個方法會建立所需型別的例項,然後給這個方法新增 @Bean
註解,同時也可以通過 name 屬性指定一個不同的名字,當然不寫也沒問題。例如下面程式碼
@Bean(name = "beanName")
public Bean bean() {
return new Bean();
}
假如你需要另外一個 Bean 作為你建立這個 Bean 的引數的話,你也可以通過下面的方式進行配置
public Bean bean(AnotherBean anotherBean) {
return new Bean(anotherBean);
}
之後的事情就交給 Spring 去完成吧,你依舊可以像前面提及的方式去進行依賴注入。
通過 XML 裝配 Bean
在我們專案開發中,我們可以發現,通過註解和 Java 配置檔案裝配 Bean,最大的好處就是配置方便,直觀等等,但是其弊端也顯而易見,以硬編碼的方式寫入到 Java 程式碼中,當修改其中的程式碼時,是需要重新編譯的。
XML 相對起前面兩種方式,也有其相應的利弊。最大的好處莫過於對其做修改,無需編譯程式碼,只需要重啟程式即可載入新的配置。下面就來介紹下通過 XML 配置裝配 Bean。
建立 XML 配置規範
如程式碼所示
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- congifuration here ... -->
</beans>
宣告 <bean>
<bean id="beanName" class="package.Bean">
這裡聲明瞭一個很簡單的 Bean,建立這個 Bean 的類通過 class
屬性來指定的,並且要使用許可權定的類名。同時也要給 bean 宣告一個 ID,當然你在這裡不宣告也沒什麼問題,bean 本身會自動得到一個 ID 名 package.Bean#0
。不過你之後也可能會引用到這個 Bean,所以就加個 ID 咯。
到這裡,其實 XML 的配置已經完成了,你可以在你的程式碼中進行依賴注入了。但是我們的物件初始化的時候,我們可能還要注入一些屬性。在 Spring 中,當發現到 <bean>
元素時,會呼叫這個元素中指向的類的預設建構函式來建立 bean。在 XML 的配置中,沒法像 Java Config 那樣靈活地配置。接下來就介紹如何在初始化 Bean 時注入屬性
藉助構造器注入初始化 Bean
當你採用構造器注入時,有兩種基本的配置方案可供選擇:
<constructor-arg>
元素- 使用 c- 名稱空間(只適用 Spring 3.0 後的版本)
下面舉例採用 <constructor-arg>
元素:
<bean id="beanName" class="package.Bean">
<constructor-arg ref="beanName2" />
</bean>
這樣,當 Spring 遇到這個 <constructor-arg>
元素會告知 Spring 將一個 ID 為 beanName2 的 bean 引用傳遞到 Bean 的構造器中。
裝配字面量以及集合
我們也可以將字面量注入構造器中,我們先建立一個 Bean3 類
public class Bean3 () {
String str1;
String str2;
public Bean3(String str1, String str2) {
this.str1 = str1;
this.str2 = str2;
}
public void log() {
sout(str1 + " " + str2);
}
}
然後再在 xml 檔案中進行以下配置
<bean id="beanName3" class="package.Bean3">
<constructor-arg value="hello" />
<constructor-arg value="world" />
</bean>
接下來介紹下如何裝配集合,其實只需要簡單在 <constructor-arg>
中宣告集合即可,如下所示:
<constructor-arg>
<list>
<value>str1</value>
<value>str2</value>
</list>
</constructor-arg>
-----------------------------------------------
<constructor-arg>
<list>
<ref bean="beanName" />
<ref bean="beanName" />
<ref bean="beanName" />
</list>
</constructor-arg>
設定屬性值
假設我們現在有這麼一個類,沒有任何的構造器(除了隱式的構造器),它沒有任何的強依賴。
public void TestBean() {
private String str1;
private List<String> strList;
// set()...
public void log() {
sout(str1);
fore strList {
sout(strList + " ");
}
}
}
就如上面類所示,它並不強制我們裝配任何的屬性,但是當你不設定屬性屬性去呼叫 log() 方法的時候,毫無疑問會報空指標異常。所以,我們要設定這些屬性,可以藉助 property
元素的 value
屬性實現該功能
<bean id="testBean" class="package.TestBean">
<property name="str1" value="hello" />
<property name="strList">
<list>
<value>world</value>
<value>and</value>
<value>spring</value>
</list>
</property>
</bean>
這樣就可以實現將量注入 bean 中了。
小結
Spring 有多種方案來配置 Bean,這給我們提供了許多解決問題的思路,Spring 的配置也是可以相互搭配的,就像我們熱衷與使用自動裝配,但是有時 xml 裝配或者 Java Config 裝配才是最優的選擇,這取決我們工作中遇見的情況。
順帶一提,若註解與 XML 同時使用,XML 的優先順序要高於註解。這樣做的好處是,需要對某個 Bean 做修改,只需要修改配置檔案即可