我命由我不禁天 --哪吒java
建議101:注意Class類的特殊性mysql
建議102:適時選擇getDeclaredXXX和getXXXsql
建議103:反射訪問屬性或方法時Accessible設置爲true數據庫
建議104:使用forName動態加載類文件數組
建議105:動態加載不適合數組框架
建議106:動態代理可使代理模式更加靈活ide
建議107:使用反射增長修飾模式的普適性函數
建議108:反射讓模板方法模式更強大工具
建議109:不須要太多關注反射效率性能
建議101:注意Class類的特殊性
Java語言是先把Java源文件編譯成後綴爲class的字節碼文件,而後經過classLoader機制把這些類加載到內存中。最後生成實例執行。
class類是Java的反射入口,只有在得到一個類的描述對象後才能動態的加載、調用,通常得到class對象的三種方法:
一、類屬性加載:如String.class
二、對象的getClass方法:如new String.getClass()
三、forName方法加載:如Class.forName("java.lang.String")
得到class對象以後,就能夠經過getAnnotation()得到註解,經過getMethods()得到方法,經過getConstructors()得到構造函數等。
建議102:適時選擇getDeclaredXXX和getXXX
getMethod方法得到的是全部public訪問級別的方法,包括從父類繼承的方法。
getDeclaredMethod得到的是自身類的方法,包括公用的(public)方法、私有(private)方法,並且不受限於訪問權限。
建議103:反射訪問屬性或方法時Accessible設置爲true
Java中經過反射執行一個方法:獲取一個方法對象,而後根據isAccessible返回值肯定是否可以執行,若是返回false,則調用setAccessible(true),而後再調用invoke執行方法:
Method method= ...; //檢查是否能夠訪問 if(!method.isAccessible()){ method.setAccessible(true); } //執行方法 method.invoke(obj, args);
經過反射方法執行方法時,必須在invoke以前檢查Accessible屬性。
建議104:使用forName動態加載類文件
動態加載是指程序運行時加載須要的類庫文件,對Java程序來講,雷哥類文件在啓動時或首次初始化時會被加載到內存中,而反射則能夠在運行時再決定是否須要加載。
動態加載的意義在於:加載一個類表示要初始化該類的static變量,特別是static代碼塊,在這裏能夠作大量的工做,好比註冊,初始化環境等等。
對於動態加載最經典的應用就是數據庫驅動程序的加載片斷,代碼以下:
//加載驅動 Class.forName("com.mysql..jdbc.Driver"); String url="jdbc:mysql://localhost:3306/db?user=&password="; Connection conn =DriverManager.getConnection(url); Statement stmt =conn.createStatement();
當程序動態加載該驅動時,也就是執行到Class.forName("com.mysql..jdbc.Driver")時,Driver類會被加載到內存中,因而static代碼塊開始執行,也就是把本身註冊到DriverManager中。
forName只是把一個類加載到內存中,並不保證由此產生一個實例對象,也不會執行任何方法,之因此會初始化static代碼,那是由類加載機制所決定的,而不是forName方法決定的。也就是說,若是沒有static屬性或static代碼塊,forName就是加載類,沒有任何的執行行爲。
總而言之,forName只是把一個類加載到內存中,而後初始化static代碼。
建議105:動態加載不適合數組
建議106:動態代理可使代理模式更加靈活
Java的反射框架提供了動態代理(Dynamic Proxy)機制,容許在運行期對目標類生成代理,避免重複開發。咱們知道一個靜態代理是經過主題角色(Proxy)和具體主題角色(Real Subject)共同實現主題角色(Subkect)的邏輯的,只是代理角色把相關的執行邏輯委託給了具體角色而已,一個簡單的靜態代理以下所示:
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定義必須實現的,它完成了對真實方法的調用。
經過InvocationHandler接口的實現類來實現,全部的方法都是有該Handler進行處理的,即全部被代理的方法都是由InvocationHandler接管實際的處理任務。
代碼以下:
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方法中讀取持久化的數據便可實現,並且還能實現動態切入的效果。
建議107:使用反射增長修飾模式的普適性
裝飾模式(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(); }
此類代碼只是一個比較通用的裝飾模式,只須要定義被裝飾的類及裝飾類便可,裝飾行爲由動態代理實現,實現了對裝飾類和被裝飾類的徹底解耦,提供了系統的擴展性。
建議109:不須要太多關注反射效率
反射的效率相對於正常的代碼執行確實低不少,但它是一個很是有效的運行期工具類,只要代碼結構清晰、可讀性好那就先開發出來,等到進行性能測試時有問題再優化。
最基本的編碼規則:"Don't Repeat Yourself"