轉載自某SDN-4O4NotFoundjava
Spring 4.0版本中更新了不少新功能,其中比較重要的一個就是對帶泛型的Bean進行依賴注入的支持。Spring4的這個改動使得代碼能夠利用泛型進行進一步的精簡優化。spring
泛型依賴注入就是容許咱們在使用spring進行依賴注入的同時,利用泛型的優勢對代碼進行精簡,將可重複使用的代碼所有放到一個類之中,方便之後的維護和修改。同時在不增長代碼的狀況下增長代碼的複用性。下面咱們用一個例子來簡單講解一下泛型的優勢:數據庫
假設咱們已經定義好了兩個實體類Student和Faculty:ide
public class Student { private int id; private String name; private double grade; public Student(int id, String name, double grade) { this.id = id; this.name = name; this.grade = grade; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", grade=" + grade + "]"; }
}
public class Faculty { private int id; private String name; private String evaluation; public Faculty(int id, String name, String evaluation) { this.id = id; this.name = name; this.evaluation = evaluation; } @Override public String toString() { return "Faculty [id=" + id + ", name=" + name + ", evaluation=" + evaluation + "]"; }
}
而後咱們須要持久層Bean來調用Student和Faculty裏面的方法。在Spring支持泛型依賴注入以前,咱們須要爲兩個實體類分別定義一個持久層Bean,而後從數據庫獲取咱們的實體類,再調用實體類中的方法。使用這種原始方法的代碼以下:工具
public class StudentRepository { public Student getBean(String beanName) { //獲取對應的Student } public void printString(Student s) { System.out.println(s); }
}
public class FacultyRepository { public Faculty getBean(String beanName) { //獲取對應的Faculty } public void printString(Faculty f) { System.out.println(f); } }
你們能夠看到,這樣的代碼每一個實體類都須要編寫一個新的持久層Bean,每個持久層Bean中的實體類類型都是寫死的,複用性不好。更重要的是,因爲每一個持久層Bean中所包含的實體類不一樣,持久層Bean中重複的方法(如上面例子中的printString)須要在每個持久層Bean中都實現一次,這大大增長了代碼的維護成本。學習
固然,有一些方法能夠部分解決這個問題。好比咱們能夠定義一個持久層Bean的父類BaseRepository,而後在裏面編寫一個通用的pirntString方法:測試
public class BaseRepository { public void printString(Object o) { System.out.println(o); } }
接着,咱們能夠在各個持久層Bean中調用BaseRepository的方法來實現printString:優化
public class StudentRepository extends BaseRepository{ public Student getBean(String beanName) { //獲取對應的Student } public void printString(Student s) { super.printString(s); } }
這樣的話,printString的實現實際上只編寫了一遍,所以咱們提升了代碼的複用性。同時,當printString方法不是簡單的打印到控制檯,而具備複雜的代碼和邏輯時,咱們能夠把代碼所有放在BaseRepository中,方便之後的修改和維護。可是,這種方法仍然要求每個持久層Bean編寫一個printSring方法來調用父類的方法,儘管這個方法只有簡單的一行,當相似的方法多起來以後代碼的數量仍是很可觀的。this
除了加入父類以外,還有一些其餘的方法能夠減小代碼量,提升代碼的複用性。好比咱們能夠在父類中加入setter方法使得業務層能夠爲持久層手工注入實體類的類別(如Student.class),可是並無很是好的解決方案。lua
可是當咱們使用泛型時,這些問題就迎刃而解了。咱們只須要定義一個持久層Bean,BaseRepository,也就是上面例子中的父類,而不須要任何子類:
public class BaseRepository<T> { public T getBean(String beanName) { //獲取對應的t } public void printString(T t) { System.out.println(t); } }
這個持久層Bean能夠包含全部咱們在持久層想要複用的方法。經過泛型,咱們的持久層代碼能夠用在全部實體類身上,而且咱們還能夠經過繼承方便的添加某些實體類特有的方法。咱們沒有增長額外的代碼,可是提升了代碼複用程度,同時咱們把可重複使用的代碼所有集中起來,方便了之後的維護和修改。
上面所講的內容都是泛型自己的優勢,和Spring 4.0的泛型依賴注入並無直接聯繫。可是,Spring 4.0開始支持的泛型依賴注入對於咱們使用泛型很是重要:在Spring 4.0以前,Spring的依賴注入功能是不能自動識別上面例子中泛型的類,而給不一樣的持久層Bean加以區分的。所以在Spring 4.0以前,BaseRepository<Student>和BaseRepository<Faculty>會被認爲是同一類型,通常須要用名字等其餘方式加以區分。可是如今,Spring會正確的識別聲明的泛型類別,而且根據泛型給持久層Bean進行分類。因此Student和Faculty的持久層Bean能夠被正確的區分,而且注入到上一層。這爲咱們在代碼中使用泛型提供了極大的便利。
下面咱們就來看看使用泛型的Bean的依賴注入應該如何實現:
使用泛型Bean的依賴注入與普通Bean的依賴注入在實現方法上基本相同,一樣能夠經過xml配置文件和註解兩種方式進行依賴注入。可是因爲泛型中尖括號(「<>」)的存在,使得xml配置文件依賴注入過程當中會出現編譯報錯的狀況。有的編譯器即便對尖括號進行轉義也依然會報錯。所以,爲了不沒必要要的麻煩,建議你們使用註解的方式進行帶泛型Bean的依賴注入。
使用註解進行依賴注入有以下幾種方式:
與普通Bean同樣,咱們能夠利用註解@Configuration來聲明咱們須要的使用泛型的Bean,而且進行依賴注入。
首先咱們須要新建一個類,做爲咱們的配置類:
@Configuration public class MyConfiguration { }
其中,註解@Configuration的做用是告訴spring這個類是一個配置類,這樣Spring就會自動掃描這個類中聲明的全部Bean,並把它們加入Spring容器中。不過在此以前,咱們須要在spring的配置文件中添加component-scan:
<context:component-scan base-package="com.somepackage.*</context:component-scan>
以後註解@configuration纔會被掃描到,裏面聲明的Bean纔會被添加進Spring容器中
在這以後,咱們就可使用註解對帶泛型的Bean進行依賴注入了。
首先,咱們須要聲明兩個須要用到的持久層Bean,一個是Student的持久層Bean,另一個是Faculty的。只有在聲明瞭這兩個Bean而且添加到Spring容器中後,Spring才能爲咱們進行依賴注入。
在配置類中聲明這兩個Bean的方法以下:
@Configuration public class MyConfiguration { @Bean public BaseRepository<Student> studentRepository() { return new BaseRepository<Student>() {}; } @Bean public BaseRepository<Faculty> facultyRepository() { return new BaseRepository<Faculty>() {}; } }
其中,註解@Bean與Spring的正常使用方法相同,就是聲明一個新的Bean。Spring在掃描配置類時,就會把這裏聲明的Bean加入到Spring容器中,供之後使用。這裏每一個Bean的名稱就是方法名,如studentRepository,而Bean的類型就是返回的Object的類型(不是方法的返回類型,方法的返回類型能夠是Interface等不能實例化的類型)。
若是你還須要聲明其餘的Bean,好比你不須要從數據庫獲取數據,也能夠把它們加入到這個配置類中。
而後咱們就能夠定義咱們的業務層Bean,而且用業務層Bean調用持久層的方法來對數據進行操做。咱們這裏使用printString方法做爲例子:
>@Service public class ExampleService { @Autowired private BaseRepository<Student> studentRepo; //自動注入BaseRepository<Student>() {} @Autowired private BaseRepository<Faculty> facultyRepo; //自動注入BaseRepository<Faculty>() {} public void test() { Student s = studentRepo.getBean("studentBean"); studentRepo.printString(s); Faculty f = facultyRepo.getBean("facultyBean"); facultyRepo.printString(f); } }
在業務層中,咱們可使用註解@Autowired進行依賴注入。@Autowired默認按照字段的類進行依賴注入,而Spring4的新特性就是把泛型的具體類型(如上文業務層中BaseRepository<Student>中的Student)也做爲類的一種分類方法(Qualifier)。這樣咱們的studentRepo和facultyRepo雖然是同一個類BaseRepository,可是由於泛型的具體類型不一樣,也會被區分開。
這裏我先建立了兩個實體類實例,而且加入到了剛纔提到的配置類中。這樣這兩個Bean就會被加入到Spring容器之中,而咱們能夠在getBean方法當中獲取他們。這兩個Bean的名字分別是studentBean和facultyBean,與業務層Bean中填寫的名字保持一致:
@Bean public Student studentBean() { return new Student(1, "Anna", 3.9); } @Bean public Faculty facultyBean() { return new Faculty(2, "Bob", "A"); }
固然,若是你有其餘方法可以獲取到實體類,好比你的工程整合了Hibernate ORM或者其餘工具來鏈接數據庫,就不須要向Spring容器中加入對應的Bean了,getBean方法的實現也能夠相應的改變。我這裏只是用這兩個實體類的Bean做爲例子。
而後當咱們調用業務層的test方法時,控制檯打印的結果是:
Student [id=1, name=Anna, grade=3.9] Faculty [id=2, name=Bob, evaluation=A]
而當咱們在業務層裏試圖錯誤的調用方法:
facultyRepo.printString(s);
的時候,會出現編譯錯誤。
讀到這裏,可能有的人已經發現了,這個例子存在兩個疑點。第一,這個例子不能證實咱們在運行期成功實現了依賴注入,由於咱們在運行期爲printString方法傳入了Student和Faculty的實例。第二,咱們在聲明Bean的時候,聲明的類不是BaseRepository,而是BaseRepository的一個匿名子類。
爲了解答這兩個問題,我在BaseRepository中定義了一個新的方法:
public void printType() { Type genericSuperclass = this.getClass().getGenericSuperclass(); Class<T> entityClass = (Class<T>) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; System.out.println(entityClass); }
這段代碼前兩行的做用是在帶泛型的類中,在運行期肯定泛型T的類。若是須要在BaseRepository中使用到T在運行期的具體類型,就應該使用這個方法來獲取。下面是一個比較詳細的解釋:
因爲java的擦除機制,泛型T只在編譯期有效,在運行期會被擦除,因此咱們在運行期不能直接得到T的類,一些對通常類有效的方法,好比T.class和t.getClass()對泛型都是非法的。所以咱們須要經過反射機制獲取T的類。
這段代碼第一行的做用是獲取當前類的父類,而後在第二行中經過查看父類的類參數得到當前類泛型的類。也就是說,這是一個經過父類的參數來查看子類中泛型的具體類型的方法。所以,這個方法有一些使用上的要求:首先,這個方法必須被帶泛型類的子類所使用,帶泛型類自己是不能使用這個方法的。另外,這個子類在泛型的位置必須繼承一個具體的類,而不是泛型T。舉個例子,當有一個類繼承BaseBaen<A>時,這個方法就可使用,而繼承BaseBean<T>的時候就不能使用,由於A是具體的類,而T是泛型,在運行期就算咱們從父類中取到了T,由於有擦除機制,咱們仍然沒法得知T是一個什麼類。
值得一提的是,Spring4也是經過一樣的方法添加了對泛型依賴注入的支持。所以咱們若是想使用Spring4的新功能,在定義Bean的時候就必須定義爲泛型類的子類,如上面例子中的new BaseBean<A>() {}。這個Bean是BaseBean的一個匿名子類,繼承的是BaseBean<A>。這樣的話Spring就能夠正確獲取到泛型T的類(A),而且以此爲根據幫助咱們實行依賴注入。
Spring的文檔和源代碼裏都有關於註解依賴注入的說明,你們有興趣的話能夠去看一下。
與此同時,咱們會發現上面的printType方法是不接收任何實例的,所以這個方法能夠幫咱們判斷泛型的依賴注入是否成功。爲了測試,我對業務層的test方法進行了以下修改:
public void test() { Student s = studentRepo.getBean("studentBean"); //studentRepo.printString(s); studentRepo.printType(); Faculty f = facultyRepo.getBean("facultyBean"); //facultyRepo.printString(f); facultyRepo.printType(); }
而後當咱們調用test方法進行測試時,控制檯會打印如下信息:
class com.somepackage.Student class com.somepackage.Faculty
這些信息說明咱們在沒有傳入實例的狀況下也正確獲取到了泛型T的類,泛型的依賴注入成功了。
注:
1. 前文提到的@Bean註解聲明Bean的方法也可使用在@Component註解標註的類當中,可是Spring建議這種作法只在工廠類中使用,並不建議大規模使用。另外,Spring對不在@Component註解標註的配置類中聲明的Bean的關聯上有一些限制,詳細的狀況請參照Spring文檔。關於註解@Component的正確使用方法,請看下一小節。
2. 除了使用@Autowired註解進行依賴注入外,咱們還可使用@Resource註解進行依賴注入。由於@Resource是優先根據名字進行依賴注入,咱們最好讓字段的名字與Bean名字相同。
上一小節咱們講述了利用@Configuration註解標註的配置類進行泛型依賴注入的實現方法和部分原理。其中咱們提到,若是想要Spring的泛型依賴注入成功,咱們必須把Bean定義爲使用泛型的類的子類。而定義一個子類最多見的方法是定義一個新的類,而後進行繼承。
所以,咱們可使用@Component註解以及它的子註解(如@Controller,@Service和@Repository)來聲明一個新的Bean。如上一小節所說,這個子類在泛型的位置必須繼承一個具體的類型,而不能繼承泛型T,不然Spring的自動依賴注入不會成功。
除此以外,這些註解的使用方法都與沒有泛型時徹底相同,下面咱們就來看一下具體的代碼:
我爲前面的兩種BaseRepository編寫了兩個子類StudentRepository和FacultyRepository:
@Repository public class StudentRepository extends BaseRepository<Student> { }
@Repository public class FacultyRepository extends BaseRepository<Faculty> { }
而且使用註解@Repository進行標註。這樣的話,Spring在掃描時將會掃描到這兩個類,並建立兩個對應的Bean加入到Spring容器中。固然,若是你想要正確使用Spring的自動掃描功能,須要在Spring配置文件中加入component-scan,詳細的作法請參考上一小節。
須要注意的是,使用了@Repository註解就已經往Spring容器中加入了一個Bean。所以,若是你在上一小節編寫了@Configuration配置類,請務必把@Configuration註解註釋掉,讓Spring再也不掃描這個配置類,或者把配置類中兩個持久層Bean的@Bean註解註釋掉,讓Spring再也不掃描這兩個持久層。不然Spring在使用@Autowired註解進行依賴注入時會由於同一類型的Bean有兩個而報錯。
下面是咱們的業務層代碼:
public class ExampleService { @Autowired private BaseRepository<Student> studentRepo; //自動注入BaseRepository<Student>() {} @Autowired private BaseRepository<Faculty> facultyRepo; //自動注入BaseRepository<Faculty>() {} public void test() { Student s = studentRepo.getBean("studentBean"); studentRepo.printString(s); studentRepo.printType(); Faculty f = facultyRepo.getBean("facultyBean"); facultyRepo.printString(f); facultyRepo.printType(); } }
業務層的代碼與上一小節相同,沒有作任何修改。注意在須要依賴注入的兩個字段中,咱們聲明的類型仍然是使用泛型的類BaseRepository,而不是咱們剛纔定義的子類StudentRepository和FacultyRepository。實際上,咱們根本不須要知道這些子類的類型,就能夠調用子類的方法,這正是Spring依賴注入的強大之處。
當咱們調用test方法時,控制檯會打印出如下信息:
Student [id=1, name=Anna, grade=3.9] class com.somepackage.Student Faculty [id=2, name=Bob, evaluation=A] class com.somepackage.Faculty
從這些信息咱們能夠看到,Spring在聲明Bean的類型與依賴注入目標類型不一樣的狀況下也能夠成功注入。這是由於Spring4開始將泛型的具體類型做爲Bean分類的一種方法(Qualifier),所以Spring可以成功區分BaseRepository<Student>和BaseRepository<Faculty>,以及他們的子類。
可是,這個例子也存在一個問題:由於依賴注入的地方聲明的是父類BaseRepository,咱們如何斷定Spring爲咱們注入的是子類StudentRepository和FacultyRepository,仍是父類BaseRepository呢?實際上咱們根本不用擔憂這個問題,由於咱們根本沒有聲明任何父類BaseRepository類型的Bean,只聲明瞭子類類型的Bean。因此若是Spring依賴注入成功了,就必定注入的是子類類型的Bean。可是在這裏,咱們也經過代碼驗證一下咱們的這個猜測。
爲了進行驗證,我在StudentRepository和FacultyRepository中覆蓋了父類BaseRepository的printString方法:
@Repository
public class StudentRepository extends BaseRepository<Student> { @Override public void printString(Student s) { System.out.println("I am StudentRepo - " + s.toString()); } }
@Repository public class FacultyRepository extends BaseRepository<Faculty> { @Override public void printString(Faculty f) { System.out.println("I am FacultyRepo - " + f.toString()); } }
而後當咱們調用業務層的test方法進行測試時,控制檯打出了以下信息:
I am StudentRepo - Student [id=1, name=Anna, grade=3.9] class com.hpe.bboss.autotest.dao.Student I am FacultyRepo - Faculty [id=2, name=Bob, evaluation=A] class com.hpe.bboss.autotest.dao.Faculty
這說明Spring爲咱們注入的是咱們所但願的子類StudentRepository和FacultyRepository,而不是父類BaseRepository。
注:
1. 當@Component註解標註的多個子類同時繼承一個父類,而且泛型的具體類型也相同時,按照以上方法進行依賴注入會拋出異常。這是由於@Autowired註解默認只有一個Bean與指定字段的類型相同,當擁有多個Bean知足條件的時候,就會拋出異常。這個問題的解決辦法有使用@Primary註解,使用@Qualifier註解和它的子註解,使用Bean名字注入等。因爲這個問題是Spring依賴注入的問題,而不是泛型依賴注入獨有的,所以再也不贅述,請你們查閱Spring文檔和其餘資料來得到具體解決辦法。
2. 泛型賴注入並不只限於在持久層使用。咱們也能夠在持久層使用泛型依賴注入的基礎上,在業務層等其餘地方也使用泛型依賴注入。相關的例子在網上很好找到,我就不復制粘貼了,有興趣的話請自行查閱。
前文所講的兩種依賴注入方式,本質上是兩種不一樣的聲明Bean的方式。如前文所說,Spring對這兩種聲明方式都擁有很好的支持,可是這兩種聲明方式自己仍是擁有比較大的差別。第一種方式中,咱們經過@Configuration註解標註配置類來進行聲明。第二種方式中,咱們經過註解直接在子類進行聲明。下面我就來簡單探討一下兩種方式的優劣。
第一種聲明方式中,全部的Bean都會在配置類中進行聲明。所以在後續進行維護時,咱們不須要查看每一個類的源代碼就能夠對Bean的狀態進行一些修改。另外,這種方式也意味着咱們不須要爲每個Bean都建立一個子類,使得目錄的管理變得簡單。可是使用這種方法意味着咱們每聲明一個新的Bean就須要對配置類添加一個方法和至少一個註解,而且有時還須要向匿名子類中添加一些方法,在Bean數量不少時配置類的長度會變得很長,不便於理解和管理。
而第二種聲明方式中,咱們根本不須要維護配置文件,全部聲明Bean所須要的工做,例如名字,類,加入Spring容器等,都由一個註解完成。與此同時,因爲子類的存在,咱們能夠很方便的進行添加方法和字段,覆蓋方法等工做。可是使用這種方法也意味着當咱們須要對Bean的狀態進行修改時,咱們必須找到相應的類才能進行操做。並且大量的子類會讓咱們的目錄更加繁雜,尤爲是空子類,自己沒有太大意義,卻讓目錄的管理變得很麻煩。
綜上所述,兩種方式各有各的優缺點,而使用哪一種方法應該根據項目的具體狀況而定。通常來講,當子類中空類較多時,可能使用第一種方法比較合適,反之第二種方法比較合適。在一些難以決定的狀況下,兩種方法同時使用有時也是一種能夠考慮的選擇。可是兩種方法同時使用會提升維護的難度,建議謹慎使用。
Spring 4.0版本新加入的泛型依賴注入功能是一個很實用的功能。它幫助咱們利用泛型極大的精簡了代碼,下降了維護成本。根據我此次的學習和使用來看,Spring對泛型依賴注入的支持整體質量仍是很不錯的。泛型依賴注入的實現與普通依賴注入差異並不大,學習起來簡單易懂,使用上也沒有什麼難度。但願看到這篇文章的你們在之後使用Spring的時候也試着試用一下泛型依賴注入。
不過,Spring4的泛型依賴注入也有一些能夠改進的地方。我此次研究Spring泛型注入的初衷就是找到一種簡單的注入方法,可讓我在使用Spring依賴注入的同時,儘量的減小聲明的類的數量。可是通過我這段時間的學習,我發現Spring目前必須爲每個Bean聲明一個新的類,不管是匿名子類仍是空子類,不然Spring就不能正確進行依賴注入。可是當咱們不須要往子類裏添加任何功能時,匿名子類或者空子類過多,這個配置類就變得很低效,不管是聲明仍是維護管理都很是麻煩。我但願之後的Spring更新時,可以自動爲咱們建立這些匿名子類,或者經過一些別的方式,讓咱們既不須要配置類又不須要子類就能夠成功的聲明一些使用泛型的Bean,而且根據泛型的類型進行依賴注入。好比我但願這樣聲明Bean
@Repository public class BaseRepository<T> { public void printString(T t) { System.out.println(t); } public void printType() { Type genericSuperclass = this.getClass().getGenericSuperclass(); Class<T> entityClass = (Class<T>) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; System.out.println(entityClass); } }
而後在進行依賴注入的時候,Spring能夠經過字段的類型來自動生成匿名子類,並進行注入:
@Autowired private BaseRepository<Student> studentRepo; //自動注入BaseRepository<Student>() {} @Autowired private BaseRepository<Faculty> facultyRepo; //自動注入BaseRepository<Faculty>() {}
若是Spring能夠作到這樣的話,我相信咱們的開發會變的更加高效。