Java的反射框架提供了動態代理(Dynamic Proxy)機制,容許在運行期對目標類生成代理,避免重複開發。咱們知道一個靜態代理是經過主題角色(Proxy)和具體主題角色(Real Subject)共同實現主題角色(Subject)的邏輯的,只是代理角色把相關的執行邏輯委託給了具體角色而已,一個簡單的靜態代理以下所示:java
interface Subject { // 定義一個方法 public void request(); } // 具體主題角色 class RealSubject implements Subject { // 實現方法 @Override public void request() { // 實現具體業務邏輯 } } class Proxy implements Subject { // 要代理那個實現類 private Subject subject = null; // 默認被代理者 public Proxy() { subject = new RealSubject(); } // 經過構造函數傳遞被代理者 public Proxy(Subject _subject) { subject = _subject; } @Override public void request() { before(); subject.request(); after(); } // 預處理 private void after() { // doSomething } // 善後處理 private void before() { // doSomething } }
這是一個簡單的靜態代理。Java還提供了java.lang.reflect.Proxy用於實現動態代理:只要提供一個抽象主題角色和具體主題角色,就能夠動態實現其邏輯的,其實例代碼以下:算法
interface Subject { // 定義一個方法 public void request(); } // 具體主題角色 class RealSubject implements Subject { // 實現方法 @Override public void request() { // 實現具體業務邏輯 } } class SubjectHandler implements InvocationHandler { // 被代理的對象 private Subject subject; public SubjectHandler(Subject _subject) { subject = _subject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 預處理 System.out.println("預處理..."); //直接調用被代理的方法 Object obj = method.invoke(subject, args); // 後處理 System.out.println("後處理..."); return obj; } }
注意這裏沒有代理主題角色,取而代之的是SubjectHandler 做爲主要的邏輯委託處理,其中invoke方法是接口InvocationHandler定義必須實現的,它完成了對真實方法的調用。spring
咱們來詳細解釋一下InvocationHandler接口,動態代理是根據被代理的接口生成的全部方法的,也就是說給定一個或多個接口,動態代理會宣稱「我已經實現該接口下的全部方法了」,那你們想一想看,動態代理是怎麼才能實現接口中的方法呢?在默認狀況下全部方法的返回值都是空的,是的,雖然代理已經實現了它,可是沒有任何的邏輯含義,那怎麼辦?好辦,經過InvocationHandler接口的實現類來實現,全部的方法都是由該Handler進行處理的,即全部被代理的方法都由InvocationHandler接管實際的處理任務。數據庫
咱們開看看動態代理的場景,代碼以下: session
public static void main(String[] args) { //具體主題角色,也就是被代理類 Subject subject = new RealSubject(); //代理實例的處理Handler InvocationHandler handler =new SubjectHandler(subject); //當前加載器 ClassLoader cl = subject.getClass().getClassLoader(); //動態代理 Subject proxy = (Subject) Proxy.newProxyInstance(cl,subject.getClass().getInterfaces(),handler); //執行具體主題角色方法 proxy.request(); }
此時就實現了,不用顯式建立代理類即實現代理的功能,例如能夠在被代理的角色執行前進行權限判斷,或者執行後進行數據校驗。框架
動態代理很容易實現通用的代理類,只要在InvocationHandler的invoke方法中讀取持久化的數據便可實現,並且還能實現動態切入的效果,這也是AOP(Aspect Oriented Programming)變成理念。ide
裝飾模式(Decorator Pattern)的定義是「動態的給一個對象添加一些額外的職責。就增長功能來講,裝飾模式相比於生成子類更爲靈活」,不過,使用Java的動態代理也能夠實現裝飾模式的效果,並且其靈活性、適應性都會更強。函數
咱們以卡通片《貓和老鼠》(Tom and Jerry)爲例,看看如何包裝小Jerry讓它更強大。首先定義Jerry的類:老鼠(Rat類),代碼以下: 工具
interface Animal{ public void doStuff(); } class Rat implements Animal{ @Override public void doStuff() { System.out.println("Jerry will play with Tom ......"); } }
接下來,咱們要給Jerry增長一些能力,好比飛行,鑽地等能力,固然使用繼承也很容易實現,但咱們這裏只是臨時的爲Rat類增長這些能力,使用裝飾模式更符合此處的場景,首先定義裝飾類,代碼以下:性能
//定義某種能力 interface Feature{ //加載特性 public void load(); } //飛行能力 class FlyFeature implements Feature{ @Override public void load() { System.out.println("增長一對翅膀..."); } } //鑽地能力 class DigFeature implements Feature{ @Override public void load() { System.out.println("增長鑽地能力..."); } }
此處定義了兩種能力:一種是飛行,另外一種是鑽地,咱們若是把這兩種屬性賦予到Jerry身上,那就須要一個包裝動做類了,代碼以下:
class DecorateAnimal implements Animal { // 被包裝的動物 private Animal animal; // 使用哪個包裝器 private Class<? extends Feature> clz; public DecorateAnimal(Animal _animal, Class<? extends Feature> _clz) { animal = _animal; clz = _clz; } @Override public void doStuff() { InvocationHandler handler = new InvocationHandler() { // 具體包裝行爲 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = null; if (Modifier.isPublic(method.getModifiers())) { obj = method.invoke(clz.newInstance(), args); } animal.doStuff(); return obj; } }; //當前加載器 ClassLoader cl = getClass().getClassLoader(); //動態代理,又handler決定如何包裝 Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler); proxy.load(); } }
注意看doStuff方法,一個裝飾類型必然是抽象構建(Component)的子類型,它必須實現doStuff方法,此處的doStuff方法委託給了動態代理執行,而且在動態代理的控制器Handler中還設置了決定裝飾方式和行爲的條件(即代碼中InvocationHandler匿名類中的if判斷語句),固然,此處也能夠經過讀取持久化數據的方式進行判斷,這樣就更加靈活了。
抽象構建有了,裝飾類也有了,裝飾動做類也完成了,那咱們就能夠編寫客戶端進行調用了,代碼以下:
public static void main(String[] args) { //定義Jerry這隻老鼠 Animal jerry = new Rat(); //爲Jerry增長飛行能力 jerry = new DecorateAnimal(jerry, FlyFeature.class); //jerry增長挖掘能力 jerry = new DecorateAnimal(jerry, DigFeature.class); //Jerry開始戲弄毛了 jerry.doStuff(); }
此類代碼只一個比較通用的裝飾模式,只須要定義被裝飾的類及裝飾類便可,裝飾行爲由動態代理實現,實現了對裝飾類和被裝飾類的徹底解耦,提供了系統的擴展性。
模板方法模式(Template Method Pattern)的定義是:定義一個操做中的算法骨架,將一些步驟延遲到子類中,使子類不改變一個算法的結構便可重定義該算法的某些特定步驟。簡單的說,就是父類定義抽象模板做爲骨架,其中包括基本方法(是由子類實現的方法,而且在模板方法中被調用)和模板方法(實現對基本方法的調度,完成固定的邏輯),它是用了簡單的繼承和覆寫機制,我麼來看一個基本的例子。
咱們常常會開發一些測試或演示程序,指望系統在啓動時自動初始化,以方便測試或講解,通常的作法是寫一個SQL文件,在系統啓動前自動導入,不過,這樣不只麻煩並且容易出錯,因而咱們就手寫了一個自動初始化數據的框架:在系統(或容器)自動啓動時自行初始化數據。但問題是每一個應用程序要初始化的內容咱們並不知道,只能由實現者自行編寫,那咱們就必須給做者預留接口,此時就得考慮使用模板方法模式了,代碼以下:
1 public abstract class AbsPopulator { 2 // 模板方法 3 public final void dataInitialing() throws Exception { 4 // 調用基本方法 5 doInit(); 6 } 7 8 // 基本方法 9 protected abstract void doInit(); 10 }
這裏定義了一個抽象模板類AbsPopulator,它負責數據初始化,可是具體要初始化哪些數據則是由doInit方法決定的,這是一個抽象方法,子類必須實現,咱們來看一個用戶表數據的加載:
public class UserPopulator extends AbsPopulator{ @Override protected void doInit() { //初始化用戶表,如建立、加載數據等 } }
該系統在啓動時查找全部的AbsPopulator實現類,而後dataInitialing實現數據的初始化。那你們可能要想了,怎麼讓容器指導這個AbsPopulator類呢?很簡單,若是是使用Spring做爲Ioc容器的項目,直接在dataInitialing方法上加上@PostConstruct註解,Spring容器啓動完畢後自動運行dataInitialing方法。具體你們看spring的相關知識,這裏再也不贅述。
如今問題是:初始化一張User表須要很是多的操做,好比先建表,而後篩選數據,以後插入,最後校驗,若是把這些都放入到一個doInit方法裏會很是龐大(即便提煉出多個方法承擔不一樣的責任,代碼的可讀性依然不好),那該如何作呢?又或者doInit是沒有任何的也無心義的,是否能夠起一個優雅而又動聽的名字呢?
答案是咱們可使用反射加強模板方法模式,使模板方法實現對一批固定的規則的基本方法的調用。代碼是最好的交流語言,咱們看看怎麼改造AbsPopulator類,代碼以下:
public abstract class AbsPopulator { // 模板方法 public final void dataInitialing() throws Exception { // 得到全部的public方法 Method[] methods = getClass().getMethods(); for (Method m : methods) { // 判斷是不是數據初始化方法 if (isInitDataMethod(m)) { m.invoke(this); } } } // 判斷是不是數據初始化方法,基本方法鑑定器 private boolean isInitDataMethod(Method m) { return m.getName().startsWith("init")// init開始 && Modifier.isPublic(m.getModifiers())// 公開方法 && m.getReturnType().equals(Void.TYPE)// 返回值是void && !m.isVarArgs()// 輸出參數爲空 && !Modifier.isAbstract(m.getModifiers());// 不能是抽象方法 } }
在通常的模板方法模式中,抽象模板(這裏是AbsPopulator類)須要定義一系列的基本方法,通常都是protected訪問級別的,而且是抽象方法,這標誌着子類必須實現這些基本方法,這對子類來講既是一個約束也是一個負擔。可是使用了反射後,不須要定義任何抽象方法,只須要定義一個基本方法鑑定器(例子中的isInitDataMethod)便可加載符合規則的基本方法。鑑別器在此處的做用是鑑別子類方法中哪些是基本方法,模板方法(例子中的dataInitaling)則須要基本方法鑑定器返回的結果經過反射執行相應的方法。
此時,若是須要進行大量的初始化工做,子類的實現就很是簡單了,代碼以下:
public class UserPopulator extends AbsPopulator { public void initUser() { /* 初始化用戶表,如建立、加載數據等 */ } public void initPassword() { /* 初始化密碼 */ } public void initJobs() { /* 初始化工做任務 */ } }
UserPopulator類中的方法只要符合基本方法鑑別器條件即會被模板方法調用,方法的數據量也再也不受父類的約束,實現了子類靈活定義基本方法、父類批量調用的功能,而且縮減了子類的代碼量。
若是你們熟悉JUnit的話,就會看出此處的實現與JUnit很是類似,JUnit4以前要求測試的方法名必須是以test開頭的,而且無返回值、無參數,並且是public修飾,其實現的原理與此很是相似,你們有興趣能夠看看Junit的源碼。
反射的效率是一個老生常談的問題,有"經驗" 的開發人員常常會使用這句話恐嚇新人:反射的效率是很是低的,不到萬不得已就不要使用。事實上,這句話前半句是對的,後半句是錯的。
反射的效率相對於正常的代碼執行確實低不少,但它是一個很是有效的運行期工具類,只要代碼結構清晰、可讀性好那就先開發起來,等到進行性能測試時證實此處性能確實有問題再修改也不遲(通常狀況下,反射並非性能的終極殺手,而代碼結構混亂、可讀性差則可能會埋下性能隱患)。咱們看這樣一個例子,在運行期得到泛型類的泛型,代碼以下:
class Utils { // 得到一個泛型類的實際泛型類型 public static <T> Class<T> getGenricClassType(Class clz) { Type type = clz.getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Type[] types = pt.getActualTypeArguments(); if (types.length > 0 && types[0] instanceof Class) { // 如有多個泛型參數,依據位置索引返回 return (Class<T>) types[0]; } } return (Class<T>) Object.class; } }
前面咱們講過,Java泛型只存在於編譯器,那爲何這個工具類能夠取得運行期的泛型類型呢?那是由於該工具只支持繼承的泛型類,若是是在Java編譯時已經肯定了泛型類的類型參數,那固然能夠經過泛型類得到了。例若有這樣一個泛型類:
abstract class BaseDao<T>{ //得到T運行期的類型 private Class<T> clz = Utils.getGenricClassType(getClass()); //根據主鍵得到一條記錄 public void get(long id){ session.get(clz,id); } } //操做user表 class UserDao extends BaseDao<String>{ }
對於UserDao類,編譯器編譯時已經明確了其參數類型是String,所以能夠經過反射的方式來獲取其類型,這也是getGenricClassType方法使用的場景。
BaseDao和UserDao是ORM中的常客,BaseDao實現對數據庫的基本操做,好比增刪改查,而UserDao則是一個比較具體的數據庫操做,其做用是對User表進行操做,若是BaseDao可以提供足夠多的基本方法,好比單表的增刪改查,哪些與UserDao相似的BaseDao子類就能夠省卻大量的開發工做。但問題是持久層的session對象(這裏模擬的是Hibernate Session)須要明確一個具體的類型才能操做,好比get查詢,須要得到兩個參數:實體類類型(用於肯定映射的數據表)和主鍵,主鍵好辦,問題是實體類類型怎麼得到呢?
子類進行傳遞?麻煩,並且也容易產生錯誤。
讀取配置問題?可行,但效率不高。
最好的辦法就是父類泛型化,子類明確泛型參數,而後經過反射讀取相應的類型便可,因而就有了咱們代碼中clz變量:經過反射得到泛型類型。如此實現後,UserDao可不用定義任何方法,繼承過來的父類操做方法已經知足基本需求了,這樣的代碼結構清晰,可讀性又好。
想一想看,若是考慮反射效率問題,沒有clz變量,不使用反射,每一個BaseDao的子類都要實現一個查詢操做,代碼將會大量重複,違反了" Don't Repeat Yourself " 這條最基本的編碼規則,這會導致項目重構、優化難度加大,代碼的複雜度也會提升不少。
對於反射效率的問題,不要作任何的提早優化和預期,這基本上是杞人憂天,不多有項目是由於反射問題引發系統效率故障的(除非是拷貝的垃圾代碼),並且根據二八原則,80%的性能消耗在20%的代碼上,這20%的代碼纔是咱們關注的重點,不要單單把反射做爲重點關注對象。
注意:反射效率低是個真命題,但由於這一點而不使用它就是個假命題。