客戶端不用調用目標對象了,直接調用代理類。最終目標方法仍是去實行了。數組
代理類的每一個方法調用目標類的相同方法,而且在調用方法時候加上系統功能的代碼app
代理和目標實現了相同的接口,有相同的方法。經過接口進行引用jvm
要爲系統中的各類接口的類增長代理功能,那將須要太多的代理類,所有采用靜態代理方法,很是麻煩。ide
JVM能夠在運行期間動態生成類的字節碼,這樣動態生成的類每每被用做代理類,即動態代理類。函數
JVM生成的動態類必須實現一個或多個接口,因此,JVM生成的動態類只能用做具備相同接口的目標類的代理測試
CGLIB庫,能夠動態生成一個類的子類(是一個個開源的庫,不是sun公司的),一個類的子類也能夠用做該類的代理,因此,若是要爲一個沒有實現接口的類生成動態代理類,那麼可使用CGLIB庫ui
代理類的各個方法一般除了要調用目標的響應方法和對外放回目標返回的結果外spa
經過Java 的API 代理
getProxyClass 方法 返回字節碼 須要告訴這個字節碼實現了哪些接口 另外須要ClassLoader (每一個Class都是由類加載器加載來的,就好像每一個孩子都有媽媽同樣) ,可是由內存出來的是沒有通過類加載器哦!因此須要指定一個~!指針
找個接口測試一下:
public class ProxyTest { public static void main(String[] args) { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 String name = clazzProxy.getName();//既然是個字節碼就能夠igetName() System.out.println(name); } }
反正獲取到了字節碼,就玩字節碼的那一套API就OK了,隨便用
獲取到構造方法:
public class ProxyTest { public static void main(String[] args) { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 String name = clazzProxy.getName();//既然是個字節碼就能夠igetName() System.out.println(name); System.out.println("開始打印構造函數相關"); Constructor[] constructors = clazzProxy.getConstructors(); for (Constructor c : constructors){ String constructorName = c.getName(); StringBuilder stringBuffer = new StringBuilder(name); //初始值 stringBuffer.append('('); Class[] parameterTypes = c.getParameterTypes(); for (Class clazzParam : parameterTypes){ stringBuffer.append(clazzParam.getName()).append(','); } if (parameterTypes != null && parameterTypes.length == 0){ stringBuffer.deleteCharAt(stringBuffer.length()-1); //刪除掉最後那個字符 } stringBuffer.append(')'); System.out.println(stringBuffer); } } }
打印結果:
代理類:只有一構造方法 參數類型也一目瞭然,只有一個有參的構造方法。 參數是個新的類型哈,後面會說到他
代理類方法的獲取:
public class ProxyTest { public static void main(String[] args) { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 System.out.println("打印全部方法:");; Method[] methods = clazzProxy.getMethods(); for (Method m : methods){ String methodName = m.getName(); System.out.println("方法名字"+methodName); Class<?>[] parameterTypes = m.getParameterTypes(); for (Class clazzParam : parameterTypes){ System.out.println("方法類型"+clazzParam.getName()); } } } }
截了一部分圖:
下面咱們建立動態代理類的實例對象,而且使用它的方法:
ublic interface InvocationHandler { /** * Processes a method invocation on a proxy instance and returns
是個接口,無法直接new對象,只能匿名對象。本身實現之
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 //clazzProxy.newInstance(); //搞到他的字節碼了已經 可是沒有不帶參數的構造方法! 因此這麼玩兒是不容許的! Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); //作好有參的構造函數 //內部類 class MyInvocationHandler implements InvocationHandler{ @Override //實現它的方法 裏面只有一個方法哦 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } //實現的是Collection的接口哦 是Collection類的代理對象 Collection proxy = (Collection)constructor.newInstance(new MyInvocationHandler());//此時傳入真實的值 此時咱們要看下InvocationHandler 究竟是咋回事 System.out.println(proxy); //proxy代理類是擁有Collection類中的全部方法哦 } }
建立了動態類,動態類去實例化對象,動態類的構造方法須要傳入一個 InvocationHandler類的參數!
能夠經過匿名內部類的方式:
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 //clazzProxy.newInstance(); //搞到他的字節碼了已經 可是沒有不帶參數的構造方法! 因此這麼玩兒是不容許的! Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); //作好有參的構造函數 //實現的是Collection的接口哦 是Collection類的代理對象 Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } });//此時傳入真實的值 此時咱們要看下InvocationHandler 究竟是咋回事 System.out.println(proxy); //proxy代理類是擁有Collection類中的全部方法哦 } }
總結:
建立實現了Collection接口的動態類並查看其名稱,分析Proxy.getProxyClass方法的各個參數
編寫列出動態類彙總的構全部構造方法和參數簽名
編寫列出動態類中的全部方法和參數簽名
建立動態類的實例對象
用反射得到構造方法
編寫一個最簡單的InvocationHandler類
調用構造方法建立動態類的實例對象,並將別寫的InvocationHandler類的實例對象傳進去
答應建立的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其餘有返回值的方法報告了異常
將建立動態類的實例對象的代理改爲匿名內部類的像是編寫。
讓jvm建立動態類及其實例對象,須要提供信息:
三個方面:
生成的類中有哪些方法,經過讓其實現哪些接口的方式進行告知
產生的類字節碼必須有一個關聯的類加載器對象
生成的類中的方法的代碼是怎樣的,也得由咱們提供。把咱們的代碼寫在一個約定好了接口對象的方法中,把對象轉給它,它調用個人方法。至關於插入了個人代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在建立動態類的實例對象的構造方法時候傳遞進去的。在上面的InvocationHandler對象的invke方法中加一點代碼,又能夠看到這些代碼被調用執行了
能夠把建立動態代理類和實例對象,一步到位: 用Proxy裏面的一個方法就OK
用Proxy.newInstance 方法直接一步就建立出代理對象 傳入的參數: 類加載器, 接口數組 , handler
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 //clazzProxy.newInstance(); //搞到他的字節碼了已經 可是沒有不帶參數的構造方法! 因此這麼玩兒是不容許的! Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); //作好有參的構造函數 //Collection接口的類加載器 Collection proxy = (Collection) Proxy.newProxyInstance( Collection.class.getClassLoader(), //這個接口的類加載器 new Class[]{Collection.class}, //數組類型的字節碼 new InvocationHandler() { //InvocationHandler 匿名內部類 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ArrayList target = new ArrayList(); System.out.println("返回以前加點代碼"); Object returnValue = method.invoke(target, args); System.out.println("返回以後加點代碼"); return returnValue; } } ); // 每次調用proxy 的方法, Handler的invoke方法就會被執行一次 都是處理全新的目標 target! proxy.add("toov5"); proxy.add("代理類的方法"); System.out.println(proxy.size()); } }
將target挪出來!~
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 //clazzProxy.newInstance(); //搞到他的字節碼了已經 可是沒有不帶參數的構造方法! 因此這麼玩兒是不容許的! Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); //作好有參的構造函數 //Collection接口的類加載器 Collection proxy = (Collection) Proxy.newProxyInstance( Collection.class.getClassLoader(), //這個接口的類加載器 new Class[]{Collection.class}, //數組類型的字節碼 new InvocationHandler() { //InvocationHandler 匿名內部類 ArrayList target = new ArrayList(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("返回以前加點代碼"); Object returnValue = method.invoke(target, args); System.out.println("返回以後加點代碼"); return returnValue; } } ); // 每次調用proxy 的方法, Handler的invoke方法就會被執行一次 都是處理全新的目標 target! proxy.add("toov5"); proxy.add("代理類的方法"); System.out.println(proxy.size()); } }
分析:
InvocationHandler對象的運行原理:
動態申城的類實現了Collection接口(能夠實現若干接口),生成的類有Collection接口彙總的全部方法和一個以下接受InvocationHandler參數的構造方法
構造方法接受一個Invocationhandler對象,接受對象了要幹什麼用呢? 該方法內部的代碼會怎樣呢?
實現Collection接口中的各個方法的代碼又是怎樣的呢? InvocationHandler接口中定義的invoke方法接接受的三個參數又是什麼意思?
Client程序嗲用objProxy.add("toov5") 方法時,涉及到三個要素: objProxy對象,add方法, 「toov5」參數
Class Proxy${
add(Object objcet){
return hanler.invoke(Object proxy, Method, Objcet[] args);
}
}
圖示:
分析先打印動態代理的實例對象,結果爲何會是null呢? 調用有基本類型返回值的方法時爲何會空指針異常?
分析爲何動態代理的實例對象的getClass() 方法返回了正確的結果呢?
分析動態代理的工做原理:
怎樣將目標類傳進去?
直接在InvocationHandler實現類中建立目標類的實例對象。能夠看運行效果和加入實質代碼,可是沒有實際意義
爲InvocationHandler實現類注入目標類的實例對象,不能採用匿名內部類的形式了
讓匿名的InvocationHandler實現類訪外面方法中的目標實現類實例對象final類型的引用變量
功能接口:
public interface Advice { void beforeMethod(Method method); void afterMethod(Method method); }
功能接口實現類:
public class MyAdvice implements Advice{ long beginTime = 0; @Override public void beforeMethod(Method method) { long beginTime = System.currentTimeMillis(); System.out.println(method.getName()+"running start time"+beginTime); } @Override public void afterMethod(Method method) { long endTime = System.currentTimeMillis(); System.out.println(method.getName()+"running end time"+endTime); }
代理及其實現測試:
public class ProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); //找到Collection的接口(就是其字節碼) 同時用這個接口相同的類加載器 //clazzProxy.newInstance(); //搞到他的字節碼了已經 可是沒有不帶參數的構造方法! 因此這麼玩兒是不容許的! Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class); //作好有參的構造函數 //Collection接口的類加載器 final ArrayList target = new ArrayList(); Collection proxy = (Collection)getProxy(target, new MyAdvice()); // 每次調用proxy 的方法, Handler的invoke方法就會被執行一次 都是處理全新的目標 target! proxy.add("toov5"); //把代理穿進去 方法傳進去 參數傳遞進去 } private static Object getProxy( final Object target, final Advice advice){ //目標對象 功能對象 Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { //InvocationHandler 匿名內部類 @Override public Object invoke(Object proxy1, Method method, Object[] args) throws Throwable { long beginTime = System.currentTimeMillis(); advice.beforeMethod(method); //系統功能抽取成爲了一個對象 經過接口形式 Object returnValue = method.invoke(target, args); advice.afterMethod(method); //系統功能抽取成爲了一個對象 經過接口形式 return returnValue; //這個返回給代理 } } ); return proxy; } }