反射和動態代理放有必定的相關性,但單純的說動態代理是由反射機制實現的,實際上是不夠全面不許確的,動態代理是一種功能行爲,而它的實現方法有不少。要怎麼理解以上這句話,請看下文。html
反射機制是 Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。經過反射咱們能夠直接操做類或者對象,好比獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至能夠運行時修改類定義。java
獲取類對象有三種方法:git
更多方法:http://icdn.apigo.cn/blog/class-all-method.pnggithub
反射要調用類中的方法,須要經過關鍵方法「invoke()」實現的,方法調用也分爲三種:編程
如下會分別演示,各類調用的實現代碼,各類調用的公共代碼部分,以下:設計模式
// 此段代碼爲公共代碼 interface People { int parentAge = 18; public void sayHi(String name); } class PeopleImpl implements People { private String privSex = "男"; public String race = "漢族"; @Override public void sayHi(String name) { System.out.println("hello," + name); } private void prvSayHi() { System.out.println("prvSayHi~"); } public static void getSex() { System.out.println("18歲"); } }
// 核心代碼(省略了拋出異常的聲明) public static void main(String[] args) { Class myClass = Class.forName("example.PeopleImpl"); // 調用靜態(static)方法 Method getSex = myClass.getMethod("getSex"); getSex.invoke(myClass); }
靜態方法的調用比較簡單,使用 getMethod(xx) 獲取到對應的方法,直接使用 invoke(xx)就能夠了。api
普通非靜態方法調用,須要先獲取類示例,經過「newInstance()」方法獲取,核心代碼以下:框架
Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method method = myClass.getMethod("sayHi",String.class); method.invoke(object,"老王");
getMethod 獲取方法,能夠聲明須要傳遞的參數的類型。ide
調用私有方法,必須使用「getDeclaredMethod(xx)」獲取本類全部什麼的方法,代碼以下:性能
Class myClass = Class.forName("example.PeopleImpl"); Object object = myClass.newInstance(); Method privSayHi = myClass.getDeclaredMethod("privSayHi"); privSayHi.setAccessible(true); // 修改訪問限制 privSayHi.invoke(object);
除了「getDeclaredMethod(xx)」能夠看出,調用私有方法的關鍵是設置 setAccessible(true) 屬性,修改訪問限制,這樣設置以後就能夠進行調用了。
1.在反射中核心的方法是 newInstance() 獲取類實例,getMethod(..) 獲取方法,使用 invoke(..) 進行方法調用,經過 setAccessible 修改私有變量/方法的訪問限制。
2.獲取屬性/方法的時候有無「Declared」的區別是,帶有 Declared 修飾的方法或屬性,能夠獲取本類的全部方法或屬性(private 到 public),但不能獲取到父類的任何信息;非 Declared 修飾的方法或屬性,只能獲取 public 修飾的方法或屬性,並能夠獲取到父類的信息,好比 getMethod(..)和getDeclaredMethod(..)。
動態代理是一種方便運行時動態構建代理、動態處理代理方法調用的機制,不少場景都是利用相似機制作到的,好比用來包裝 RPC 調用、面向切面的編程(AOP)。
實現動態代理的方式不少,好比 JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其餘的實現方式,好比利用傳說中更高性能的字節碼操做機制,相似 ASM、cglib(基於 ASM)等。
動態代理解決的問題?
首先,它是一個代理機制。若是熟悉設計模式中的代理模式,咱們會知道,代理能夠看做是對調用目標的一個包裝,這樣咱們對目標代碼的調用不是直接發生的,而是經過代理完成。經過代理可讓調用者與實現者之間解耦。好比進行 RPC 調用,經過代理,能夠提供更加友善的界面。還能夠經過代理,能夠作一個全局的攔截器。
JDK Proxy 是經過實現 InvocationHandler 接口來實現的,代碼以下:
interface Animal { void eat(); } class Dog implements Animal { @Override public void eat() { System.out.println("The dog is eating"); } } class Cat implements Animal { @Override public void eat() { System.out.println("The cat is eating"); } } // JDK 代理類 class AnimalProxy implements InvocationHandler { private Object target; // 代理對象 public Object getInstance(Object target) { this.target = target; // 取得代理對象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("調用前"); Object result = method.invoke(target, args); // 方法調用 System.out.println("調用後"); return result; } } public static void main(String[] args) { // JDK 動態代理調用 AnimalProxy proxy = new AnimalProxy(); Animal dogProxy = (Animal) proxy.getInstance(new Dog()); dogProxy.eat(); }
如上代碼,咱們實現了經過動態代理,在全部請求以前和以後打印了一個簡單的信息。
注意: JDK Proxy 只能代理實現接口的類(即便是extends繼承類也是不能夠代理的)。
JDK Proxy 爲何只能代理實現接口的類?
這個問題要從動態代理的實現方法 newProxyInstance 源碼提及:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 省略其餘代碼
來看前兩個源碼參數說明:
* @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class to implement
因此這個問題的源頭,在於 JDK Proxy 的源碼設計。若是要執意動態代理,非接口實現類就會報錯:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx
JDK 動態代理機制只能代理實現了接口的類,Cglib 是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現加強,但由於採用的是繼承,因此不能對 final 修飾的類進行代理。
Cglib 能夠經過 Maven 直接進行版本引用,Maven 版本地址:https://mvnrepository.com/artifact/cglib/cglib
本文使用的是最新版本 3.2.9 的 Cglib,在 pom.xml 添加以下引用:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.9</version> </dependency>
Cglib 代碼實現,以下:
class Panda { public void eat() { System.out.println("The panda is eating"); } } class CglibProxy implements MethodInterceptor { private Object target; // 代理對象 public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); // 設置父類爲實例類 enhancer.setSuperclass(this.target.getClass()); // 回調方法 enhancer.setCallback(this); // 建立代理對象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("調用前"); Object result = methodProxy.invokeSuper(o, objects); // 執行方法調用 System.out.println("調用後"); return result; } } public static void main(String[] args) { // CGLIB 動態代理調用 CglibProxy proxy = new CglibProxy(); Panda panda = (Panda)proxy.getInstance(new Panda()); panda.eat(); }
cglib 的調用經過實現 MethodInterceptor 接口的 intercept 方法,調用 invokeSuper 進行動態代理的,能夠直接對普通類進行動態代理。
JDK Proxy 的優點:
Cglib 框架的優點:
總結: 須要注意的是,咱們在選型中,性能未必是惟一考量,可靠性、可維護性、編程工做量等每每是更主要的考慮因素,畢竟標準類庫和反射編程的門檻要低得多,代碼量也是更加可控的,若是咱們比較下不一樣開源項目在動態代理開發上的投入,也能看到這一點。
本文全部示例代碼:https://github.com/vipstone/java-core-example.git
Java核心技術36講:http://t.cn/EwUJvWA
Java反射與動態代理:https://www.cnblogs.com/hanganglin/p/4485999.html