使用註解的方式能夠減小XML的配置,註解功能更爲強大,它既能實現XML的功能,也提供了自動裝配的功能,採用了自動裝配後,程序員所須要作的決斷就少了,更加有利於對程序的開發,這就是「約定優於配置」的開發原則。
在Spring中,它提供了兩種方式來讓Spring IoC容器發現Bean。
•組件掃描:經過定義資源的方式,讓Spring IoC容器掃描對應的包,從而把Bean裝配進來。
•自動裝配:經過註解定義,使得一些依賴關係能夠經過註解完成。
經過掃描和自動裝配,大部分的工程均可以用Java配置完成,而不是XML,這樣能夠有效減小配置和引入大量XML,它解決了在Spring 3以前的版本須要大量的XML配置的問題,這些問題曾被許多開發者詬病。因爲目前註解已經成爲Spring開發的主流,在以後的章節裏,筆者也會以註解的方式爲主介紹Spring的開發,可是請注意只是爲主,而不是所有以註解的方式去實現。由於不使用XML也存在着必定的弊端,好比系統存在多個公共的配置文件(好比多個properties和XML文件),若是寫在註解裏,那麼那些公共資源的配置就會比較分散了,這樣不利於統一的管理,又或者一些類來自於第三方,而不是咱們系統開發的配置文件,這時利用XML的方式來完成會更加明確一些,所以目前企業所流行的方式是,以註解爲主,以XML爲輔,本書的介紹也是如此。程序員
@Component(value = "role") public class Role { @Value("1") private Long id; @Value("role_name_1") private String roleName; @Value("role_note_1") private String note; }
•註解@Component表明Spring IoC會把這個類掃描生成Bean實例,而其中的value屬性表明這個類在Spring中的id,這就至關於XML方式定義的Bean的id,也能夠簡寫成@Component("role"),甚至直接寫成@Component,對於不寫的,Spring IoC容器就默認類名,可是以首字母小寫的形式做爲id,爲其生成對象,配置到容器中。
•註解@Value表明的是值的注入,這裏只是簡單注入一些值,其中id是一個long型,注入的時候Spring會爲其轉化類型。spring
如今有了這個類,可是還不能進行測試,由於Spring IoC並不知道須要去哪裏掃描對象,這個時候可使用一個Java Config來去告訴它,如代碼清單所示。
代碼清單:Java Config類編程
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class PojoConfig { }
這個類十分簡單,幾乎沒有邏輯,可是要注意兩處加粗的代碼。
•包名和代碼清單的POJO保持一致。
•@ComponentScan表明進行掃描,默認是掃描當前包的路徑,POJO的包名和它保持一致才能掃描,不然是沒有的。數組
能夠經過Spring定義好的Spring IoC容器的實現類——AnnotationConfigApplicationContext去生成IoC容器了。它十分簡單,如代碼清單所示。
代碼清單:使用註解生成Spring IoC容器ide
ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class); Role role = context.getBean(Role.class); System.err.println(role.getId());
@ComponentScan存在着兩個配置項:第1個是basePackages,它是由base和package兩個單詞組成的,而package還使用了複數,意味着它能夠配置一個Java包的數組,Spring會根據它的配置掃描對應的包和子包,將配置好的Bean裝配進來;第2個是basePackageClasses,它由base、package和class三個單詞組成的,採用複數,意味着它能夠配置多個類,Spring會根據配置的類所在的包,爲包和子包進行掃描裝配對應配置的Bean。
代碼清單:RoleService接口學習
import com.ssm.chapter10.annotation.pojo.Role; public interface RoleService { public void printRoleInfo(Role role); }
代碼清單:RoleServiceImpl類測試
import org.springframework.stereotype.Component; import com.ssm.chapter10.annotation.pojo.Role; import com.ssm.chapter10.annotation.service.RoleService; @Component public class RoleServiceImpl implements RoleService { // @Override public void printRoleInfo(Role role) { System.out.println("id =" + role.getId()); System.out.println("roleName =" + role.getRoleName()); System.out.println("note =" + role.getNote()); } }
代碼清單:配置@ComponentScan制定包掃描ui
import org.springframework.context.annotation.ComponentScan; import com.ssm.chapter10.annotation.pojo.Role; import com.ssm.chapter10.annotation.service.impl.RoleServiceImpl; @ComponentScan(basePackageClasses = {Role.class, RoleServiceImpl.class}) // @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo", "com.ssm.chapter10.annotation.service"}) // @ComponentScan(basePackages = {"com.ssm.chapter10.annotation.pojo", "com.ssm.chapter10.annotation.service"} //, basePackageClasses = {Role.class, RoleServiceImpl.class}) public class ApplicationConfig { }
•這是對掃描包的定義,能夠採用任意一個@ComponentScan去定義,也能夠取消代碼中的註釋。
•若是採用多個@ComponentScan去定義對應的包,可是每定義一個@ComponentScan,Spring就會爲所定義的類生成一個新的對象,也就是所配置的Bean將會生成多個實例,這每每不是咱們的須要。
•對於已定義了basePackages和basePackageClasses的@ComponentScan,Spring會進行專門的區分,也就是說在同一個@ComponentScan中即便重複定義相同的包或者存在其子包定義,也不會形成因同一個Bean的屢次掃描,而致使一次配置生成多個對象。
基於上述的幾點,建議不要採用多個@ComponentScan註解進行配置,由於一旦有重複的包和子包就會產生重複的對象,這每每不是真實的需求。對於basePackages和basePackageClasses的選擇問題,basePackages的可讀性會更好一些,所以在項目中會優先選擇使用它,可是在須要大量重構的工程中,儘可能不要使用basePackages定義,由於不少時候重構修改包名須要反覆地配置,而IDE不會給你任何的提示。而採用basePackageClasses,當你對包移動的時候,IDE會報錯提示,而且能夠輕鬆處理這些錯誤。
代碼清單:測試basePackages和basePackageClasses配置this
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); Role role = context.getBean(Role.class); RoleService roleService = context.getBean(RoleService.class); roleService.printRoleInfo(role); context.close();
經過學習Spring IoC容器,咱們知道Spring是先完成Bean的定義和生成,而後尋找須要注入的資源。也就是當Spring生成全部的Bean後,若是發現這個註解,它就會在Bean中查找,而後找到對應的類型,將其注入進來,這樣就完成依賴注入了。所謂自動裝配技術是一種由Spring本身發現對應的Bean,自動完成裝配工做的方式,它會應用到一個十分經常使用的註解@Autowired上,這個時候Spring會根據類型去尋找定義的Bean而後將其注入,這裏須要留意按類型(Role)的方式。spa
public interface RoleService2 { public void printRoleInfo(); }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.ssm.chapter10.annotation.pojo.Role; import com.ssm.chapter10.annotation.service.RoleService2; @Component("RoleService2") public class RoleServiceImpl2 implements RoleService2 { @Autowired private Role role = null; // @Autowired // public void setRole(Role role) { // this.role = role; // } // @Override public void printRoleInfo() { System.out.println("id =" + role.getId()); System.out.println("roleName =" + role.getRoleName()); System.out.println("note =" + role.getNote()); } /**** setter and getter ****/ public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
這裏的@Autowired註解,表示在Spring IoC定位全部的Bean後,這個字段須要按類型注入,這樣IoC容器就會尋找資源,而後將其注入。好比代碼清單10-15定義的Role和代碼清單10-23定義RoleServiceImpl2的兩個Bean,假設將其定義,那麼Spring IoC容器會爲它們先生成對應的實例,而後依據@Au-towired註解,按照類型找到定義的實例,將其注入。
IoC容器有時候會尋找失敗,在默認的狀況下尋找失敗它就會拋出異常,也就是說默認狀況下,Spring IoC容器會認爲必定要找到對應的Bean來注入這個字段,有些時候這並非一個真實的須要,好比日誌,有時候咱們會以爲這是無關緊要的,這個時候能夠經過@Autowired的配置項required來改變它,好比@Autowired(required=false)。
正如以前所談到的在默認狀況下是必須注入成功的,因此這裏的required的默認值爲true。當把配置修改成了false時,就告訴Spring IoC容器,假如在已經定義好的Bean中找不到對應的類型,容許不注入,這樣也就沒有了異常拋出,只是這樣這個字段可能爲空,讀者要自行校驗,以免發生空指針異常。在大部分的狀況下,都不須要這樣修改。
@Autowired除能夠配置在屬性以外,還容許方法配置,常見的Bean的setter方法也可使用它來完成注入
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); RoleService2 roleService2 = context.getBean(RoleService2.class); roleService2.printRoleInfo(); context.close();
@Autowired註解,它能夠完成一些自動裝配的功能,而且使用方式十分簡單,可是有時候這樣的方式並不能使用。這一切的根源來自於按類型的方式,按照Spring的建議,在大部分狀況下會使用接口編程,可是定義一個接口,並不必定只有與之對應的一個實現類。換句話說,一個接口能夠有多個實現類
import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import com.ssm.chapter10.annotation.pojo.Role; import com.ssm.chapter10.annotation.service.RoleService; @Component("roleService3") // @Primary//方法1 public class RoleServiceImpl3 implements RoleService { // @Override public void printRoleInfo(Role role) { System.out.print("{id =" + role.getId()); System.out.print(", roleName =" + role.getRoleName()); System.out.println(", note =" + role.getNote() + "}"); } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.ssm.chapter10.annotation.pojo.Role; import com.ssm.chapter10.annotation.service.RoleService; @Component public class RoleController { @Autowired // @Qualifier("roleService3")//方法2 private RoleService roleService = null; public void printRole(Role role) { roleService.printRoleInfo(role); } }
這裏的字段roleService是一個RoleService接口類型。RoleService有兩個實現類,分別是RoleServiceImpl和RoleServiceImpl3,這個時候Spring IoC容器就會犯糊塗了,它沒法判斷把哪一個對象注入進來,因而就會拋出異常,這樣@Autowired注入就失敗了。
經過上面的分析,能夠知道產生這樣的情況是由於它採用的是按類型來注入對象,而在Java中接口能夠有多個實現類,一樣的抽象類也能夠有多個實例化的類,這樣就會形成經過類型(bytype)獲取Bean的不惟一,從而致使Spring IoC相似於按類型的方法沒法得到惟一的實例化類。
爲了消除歧義性,Spring提供了兩個註解@Primary和@Qualifier,這是兩個不一樣的註解,其消除歧義性的理念不太同樣
1. 註解@Primary註解@Primary表明首要的,當Spring IoC經過一個接口或者抽象類注入對象的時候,因爲存在多個實現類或者具體類,就會犯糊塗,不知道採用哪一個類注入爲好。註解@Primary則是告訴Spring IoC容器,請優先使用該類注入。 2. 註解@Qualifier正如上面所談及的歧義性,一個重要的緣由是Spring在尋找依賴注入的時候採用按類型注入引發的。除了按類型查找Bean,Spring IoC容器最底層的接口BeanFactory,也定義了按名稱查找的方法,若是採用名稱查找的方法,而不是採用按類型查找的方法,那麼不就能夠消除歧義性了嗎?答案是確定的,而註解@Qualifier就是這樣的一個註解。