不才黃某 碼農沉思錄java
做者:幾米憧憬 原文:https://note.youdao.com/share/?id=1b50d57ce1f7544da238d2051046ccc6&type=note#/
本文系讀者投稿,已獲做者原創受權。
若是你有好文章,能夠戳這裏投稿。spring
靜態代理,工程師編輯代理類代碼,實現代理模式;在編譯期就生成了代理類。設計模式
基於 JDK 實現動態代理,經過jdk提供的工具方法Proxy.newProxyInstance動態構建全新的代理類(繼承Proxy類,並持有InvocationHandler接口引用 )字節碼文件並實例化對象返回。(jdk動態代理是由java內部的反射機制來實例化代理對象,並代理的調用委託類方法)數組
基於CGlib 動態代理模式 基於繼承被代理類生成代理子類,不用實現接口。只須要被代理類是非final 類便可。(cglib動態代理底層是藉助asm字節碼技術緩存
基於 Aspectj 實現動態代理(修改目標類的字節,織入代理的字節,在程序編譯的時候 插入動態代理的字節碼,不會生成全新的Class )框架
靜態代理是代理類在編譯期間就建立好了,不是編譯器生成的代理類,而是手動建立的類。在編譯時就已經將接口,被代理類,代理類等肯定下來。軟件設計中所指的代理通常是指靜態代理,也就是在代碼中顯式指定的代理。maven
package org.vincent.proxy.staticproxy;/** * @author PengRong * @package org.vincent.proxy.staticproxy * @date 2018/12/15 - 17:12 * @ProjectName JavaAopLearning * @Description: 靜態代理類接口, 委託類和代理類都須要實現的接口規範。 * 定義了一個貓科動物的兩個行爲接口,吃東西,奔跑。 * 做爲代理類 和委託類之間的約束接口 */public interface Cat { public String eatFood(String foodName); public boolean running(); }
package org.vincent.proxy.staticproxy;/** * @author PengRong * @package org.vincent.proxy.staticproxy * @date 2018/12/15 - 17:15 * @ProjectName JavaAopLearning * @Description: 獅子 實現了貓科動物接口Cat, 並實現了具體的行爲。做爲委託類實現 */public class Lion implements Cat { private String name; private int runningSpeed; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRunningSpeed() { return runningSpeed; } public void setRunningSpeed(int runningSpeed) { this.runningSpeed = runningSpeed; } public Lion() { } @Override public String eatFood(String foodName) { String eat = this.name + " Lion eat food. foodName = " + foodName; System.out.println(eat); return eat; } @Override public boolean running() { System.out.println(this.name + " Lion is running . Speed :" + this.runningSpeed); return false; } }
package org.vincent.proxy.staticproxy;/** * @author PengRong * @package org.vincent.proxy.staticproxy * @date 2018/12/15 - 17:19 * @ProjectName JavaAopLearning * @Description: 飼養員 實現Cat接口,做爲靜態代理類實現。代理獅子的行爲。 * 代理類中能夠新增一些其餘行爲,在實踐中主要作的是參數校驗的功能。 */public class FeederProxy implements Cat { private Cat cat; public FeederProxy(){} public FeederProxy(Cat cat) { if (cat instanceof Cat) { this.cat = cat; } } public void setCat(Cat cat) { if (cat instanceof Cat) { this.cat = cat; } } @Override public String eatFood(String foodName) { System.out.println("proxy Lion exec eatFood "); return cat.eatFood(foodName); } @Override public boolean running() { System.out.println("proxy Lion exec running."); return cat.running(); } }
package org.vincent.proxy;import org.vincent.proxy.staticproxy.Cat;import org.vincent.proxy.staticproxy.FeederProxy;import org.vincent.proxy.staticproxy.Lion;/** * @author PengRong * @package org.vincent.proxy * @date 2018/12/15 - 18:31 * @ProjectName JavaAopLearning * @Description: 靜態代理類測試 */public class staticProxyTest { public static void main(String[] args) { Lion lion = new Lion(); lion.setName("獅子 小王"); lion.setRunningSpeed(100); /** * new 靜態代理類,靜態代理類在編譯前已經建立好了,和動態代理的最大區別點 */ Cat proxy = new FeederProxy(lion); System.out.println(Thread.currentThread().getName()+" -- " + proxy.eatFood("水牛")); proxy.running(); } }
靜態代理很好的詮釋了代理設計模式,代理模式最主要的就是有一個公共接口(Cat),一個委託類(Lion),一個代理類(FeederProxy),代理類持有委託類的實例,代爲執行具體類實例方法。 上面說到,代理模式就是在訪問實際對象時引入必定程度的間接性,由於這種間接性,能夠附加多種用途。這裏的間接性就是指客戶端不直接調用實際對象的方法,客戶端依賴公共接口並使用代理類。 那麼咱們在代理過程當中就能夠加上一些其餘用途。 就這個例子來講在 eatFood方法調用中,代理類在調用具體實現類以前添加System.out.println("proxy Lion exec eatFood ");語句 就是添加間接性帶來的收益。代理類存在的意義是爲了增長一些公共的邏輯代碼。ide
靜態代理是代理類在代碼運行前已經建立好,並生成class文件;動態代理類 是代理類在程序運行時建立的代理模式。函數
動態代理類的代理類並非在Java代碼中定義的,而是在運行時根據咱們在Java代碼中的「指示」動態生成的。相比於靜態代理, 動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類中的方法。 想一想你有100個靜態代理類,如今有一個需求,每一個代理類都須要新增一個處理邏輯,你須要打開100個代理類在每一個代理方法裏面新增處理邏輯嗎? 有或者代理類有5個方法,每一個方法都須要新增一個處理邏輯, 你須要在每一個方法都手動新增處理邏輯嗎? 想一想就挺無趣的。動態代理類幫你一鍵搞定。工具
java的java.lang.reflect包下提供了Proxy類和一個 InvocationHandler 接口,這個類Proxy定義了生成JDK動態代理類的方法 getProxyClass(ClassLoader loader,Class<?>... interfaces)生成動態代理類,返回class實例表明一個class文件。能夠保存該 class 文件查看jdk生成的代理類文件長什麼樣
該生成的動態代理類繼承Proxy類,(重要特性) ,並實現公共接口。
InvocationHandler這個接口 是被動態代理類回調的接口,咱們全部須要增長的針對委託類的統一處理邏輯都增長到invoke 方法裏面在調用委託類接口方法以前或以後 結束戰鬥。
package org.vincent.proxy.dynamicproxy;/** * Created by PengRong on 2018/12/25. * 建立Person 接口 用於定義 委託類和代理類之間的約束行爲 */public interface Person{ /** * * @param name 人名 * @param dst 工做目的地 */ void goWorking(String name, String dst); /** * 獲取名稱 * @return */ String getName( ); /** * 設置名稱 * @param name */ void setName(String name); }
具體實現類,等下被委託,被代理的類 SoftwareEngineer.java
package org.vincent.proxy.dynamicproxy;/** * Created by PengRong on 2018/12/25. * 動態代理委託類實現, 實現接口 Person。 被動態生成的代理類代理 */public class SoftwareEngineer implements Person{ public SoftwareEngineer(){} public SoftwareEngineer(String name){ this.name=name; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void goWorking(String name, String dst) { System.out.println("name ="+name+" , 去 "+dst +" 工做"); } }
InvocationHandler 接口實現 PersonInvocationHandler.java
package org.vincent.proxy.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Arrays;/** * Created by PengRong on 2018/12/25. * PersonInvocationHandler 類 實現InvocationHandler接口,這個類中持有一個被代理對象(委託類)的實例target。該類別JDK Proxy類回調 * InvocationHandler 接口中有一個invoke方法,當一個代理實例的方法被調用時,代理方法將被編碼並分發到 InvocationHandler接口的invoke方法執行。 */public class PersonInvocationHandler<T> implements InvocationHandler { /** * 被代理對象引用,invoke 方法裏面method 須要使用這個 被代理對象 */ T target; public PersonInvocationHandler(T target) { this.target = target; } /** * 在 * @param proxy 表明動態生成的 動態代理 對象實例 * @param method 表明被調用委託類的接口方法,和生成的代理類實例調用的接口方法是一致的,它對應的Method 實例 * @param args 表明調用接口方法對應的Object參數數組,若是接口是無參,則爲null; 對於原始數據類型返回的他的包裝類型。 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 在轉調具體目標對象以前,能夠執行一些功能處理 */ System.out.println("被動態代理類回調執行, 代理類 proxyClass ="+proxy.getClass()+" 方法名: " + method.getName() + "方法. 方法返回類型:"+method.getReturnType() +" 接口方法入參數組: "+(args ==null ? "null" : Arrays.toString(args))); /** * 代理過程當中插入監測方法,計算該方法耗時 */ MonitorUtil.start(); Thread.sleep(1); /** 調用唄代理對象的真實方法,*/ Object result = method.invoke(target, args); MonitorUtil.finish(method.getName()); return result; } }
PersonInvocationHandler invoke 方法中添加的公共代碼,這裏簡單以統計方法執行時間爲邏輯
package org.vincent.proxy.dynamicproxy;/** * Created by PengRong on 2018/12/25. * 方法用時監控類 */public class MonitorUtil { private static ThreadLocal<Long> tl = new ThreadLocal<>(); public static void start() { tl.set(System.currentTimeMillis()); } /** * 結束時打印耗時 * @param methodName 方法名 */ public static void finish(String methodName) { long finishTime = System.currentTimeMillis(); System.out.println(methodName + "方法執行耗時" + (finishTime - tl.get()) + "ms"); } }
最後的是 怎麼建立代理類
package org.vincent.proxy.jdkdynamicProxy;import org.vincent.proxy.dynamicproxy.Person;import org.vincent.proxy.dynamicproxy.PersonInvocationHandler;import org.vincent.proxy.dynamicproxy.SoftwareEngineer;import sun.misc.ProxyGenerator;import java.io.FileOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.nio.file.Path;import java.nio.file.Paths;import java.util.Arrays;import java.util.Properties;/** * 動態代理類測試 * Created by PengRong on 2018/12/25. */public class JdkDynamicProxyTest { public static void main(String[] args) throws Exception { // 打開保存JDK動態代理生成的類文件 saveGeneratedJdkProxyFiles(); /** * 第一種方法: 經過 Proxy.newProxyInstance 方法 獲取代理對象 */ System.out.println("-------------------第一種建立代理類方法--------------"); //建立一個實例對象,這個對象是被代理的對象,委託類 Person person = new SoftwareEngineer("Vincent"); //建立一個與代理類相關聯的InvocationHandler,每個代理類都有一個關聯的 InvocationHandler,並將代理類引用傳遞進去 InvocationHandler Handler = new PersonInvocationHandler<>(person); //建立一個 代理對象 personProxy 來代理 person,建立的代理對象的每一個執行方法都會被替換執行Invocation接口中的invoke方法 Person personProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, Handler); /** 代理類信息 */ System.out.println("package = " + personProxy.getClass().getPackage() + " SimpleName = " + personProxy.getClass().getSimpleName() + " name =" + personProxy.getClass().getName() + " CanonicalName = " + "" + personProxy.getClass().getCanonicalName() + " 實現的接口 Interfaces = " + Arrays.toString(personProxy.getClass().getInterfaces()) + " superClass = " + personProxy.getClass().getSuperclass() + " methods =" + Arrays.toString(personProxy.getClass().getMethods())); // 經過 代理類 執行 委託類的代碼邏輯 personProxy.goWorking(personProxy.getName(), "深圳"); System.out.println("-------------------第二種建立代理類方法--------------"); /** * 動態代理對象步驟 * 一、 建立一個與代理對象相關聯的 InvocationHandler,以及真實的委託類實例 * 二、Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass,該類繼承Proxy類,實現 Person.java接口;JDK動態代理的特色是代理類必須繼承Proxy類 * 三、經過代理類 proxyClass 得到他的帶InvocationHandler 接口的構造函數 ProxyConstructor * 四、經過 構造函數實例 ProxyConstructor 實例化一個代理對象,並將 InvocationHandler 接口實例傳遞給代理類。 */ // 一、建立 InvocationHandler 實例並設置代理的目標類對象 Person persontwo = new SoftwareEngineer("Vincent"); InvocationHandler Handlertwo = new PersonInvocationHandler<>(persontwo); // 2 建立代理類,是一個字節碼文件, 把 proxyClass 保存起來就能看到 他繼承Proxy 類,實現Person接口 Class<?> proxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[]{Person.class}); /** 代理類信息 */ System.out.println("package = " + proxyClass.getPackage() + " SimpleName = " + proxyClass.getSimpleName() + " name =" + proxyClass.getName() + " CanonicalName = " + "" + proxyClass.getCanonicalName() + " 實現的接口 Interfaces = " + Arrays.toString(proxyClass.getInterfaces()) + " superClass = " + proxyClass.getSuperclass() + " methods =" + Arrays.toString(proxyClass.getMethods())); // 三、 經過 proxyClass 得到 一個帶有InvocationHandler參數的構造器constructor Constructor<?> ProxyConstructor = proxyClass.getConstructor(InvocationHandler.class); // 四、經過構造器建立一個 動態代理類 實例 Person stuProxy = (Person) ProxyConstructor.newInstance(Handlertwo); /** 檢測生成的類是不是代理類 */ System.out.println("stuProxy isProxy "+Proxy.isProxyClass(stuProxy.getClass())); /** 獲取 代理類關聯的 InvocationHandler 是哪一個*/ InvocationHandler handlerObject = Proxy.getInvocationHandler(stuProxy); System.out.println(handlerObject.getClass().getName()); stuProxy.goWorking(stuProxy.getName(), "廣州"); // 保存代理類 saveClass("$PersonProxy0", proxyClass.getInterfaces(), "D:/123/"); } /** * 生成代理類 class 並保持到文件中 * * @param className 生成的代理類名稱 * @param interfaces 代理類須要實現的接口 * @param pathdir 代理類保存的目錄路徑,以目錄分隔符結尾 */ public static void saveClass(String className, Class<?>[] interfaces, String pathdir) { /** * 第一個參數是 代理類 名 。 * 第二個參數是 代理類須要實現的接口 */ byte[] classFile = ProxyGenerator.generateProxyClass(className, interfaces); /** * 若是目錄不存在就新建全部子目錄 */ Path path1 = Paths.get(pathdir); if (!path1.toFile().exists()){ path1.toFile().mkdirs(); } String path = pathdir + className + ".class"; try (FileOutputStream fos = new FileOutputStream(path)) { fos.write(classFile); fos.flush(); System.out.println("代理類class文件寫入成功"); } catch (Exception e) { System.out.println("寫文件錯誤"); } } /** * 設置保存Java動態代理生成的類文件。 * * @throws Exception */ public static void saveGeneratedJdkProxyFiles() throws Exception { Field field = System.class.getDeclaredField("props"); field.setAccessible(true); Properties props = (Properties) field.get(null); props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); } }
解析JDK生成的動態代理類
saveGeneratedJdkProxyFiles方法 打開了存儲jdk生成的動態代理類 以 接口方法 goWorking 爲例講解
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package com.sun.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import org.vincent.proxy.dynamicproxy.Person;public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m4; private static Method m3; private static Method m2; private static Method m5; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void setName(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String getName() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } /** * 對接口 goWorking 的調用 轉變成 super.h.invoke(this, m5, new Object[]{var1, var2}); 調用。 * h 就是Proxy.java類的一個 InvocationHandler 接口 屬性, * 咱們在建立 動態代理類實例時候都必須 傳一個 InvocationHandler 接口的實例過去。 這裏就是剛纔咱們定義的 PersonInvocationHandler 。 * 回到事後是否是就回到了 PersonInvocationHandler.invoke方法裏面,因此 PersonInvocationHandler 是咱們生成的動態代理類的攔截器,攔截全部方法調用。 */ public final void goWorking(String var1, String var2) throws { try { super.h.invoke(this, m5, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }/** * 靜態代碼塊,根據動態代理實現的公共接口類接口方法 獲取到全部接口方法 的 Method 實例*/ static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m4 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("setName", new Class[]{Class.forName("java.lang.String")}); m3 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("getName", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m5 = Class.forName("org.vincent.proxy.dynamicproxy.Person").getMethod("goWorking", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")}); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
Jdk爲咱們的生成了一個叫$Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時默認不會保存在文件,放在內存中的,咱們在建立代理對象時,就是經過反射得到這個類的構造方法,而後建立代理對象實例。經過對這個生成的代理類源碼的查看,咱們很容易能看出,動態代理實現的具體過程。
咱們能夠對 InvocationHandler 看作一箇中介類,中介類持有一個被代理對象,被Proxy類回調。在invoke方法中調用了被代理對象的相應方法。經過聚合方式持有被代理對象的引用,把客戶端對invoke的調用最終都轉爲對被代理對象的調用。
客戶端代碼經過代理類引用調用接口方法時,經過代理類關聯的中介類對象引用來調用中介類對象的invoke方法,從而達到代理執行被代理對象的方法。也就是說,動態代理Proxy類提供了模板實現,對外提供擴展點,外部經過實現InvocationHandler接口將被代理類歸入JDK代理類Proxy。
一、經過實現InvocationHandler接口建立本身的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);
二、經過爲Proxy類指定ClassLoader對象和一組interface代理類須要實現的接口,建立動態代理類類文件,默認JDK並不會保存這個文件到文件中;能夠保存起來觀察生成的代理類結構Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
三、經過上面新建的代理clazz的反射機制獲取動態代理類的一個構造函數,其構造函數入參類型是調用處理器接口(IvocationHandler)類型 Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
四、經過構造函數實例建立代理類實例,此時需將調用處理器對象做爲參數被傳入 Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler)); 爲了簡化對象建立過程,Proxy類中的newInstance工具方法封裝了2~4,只需兩步便可完成代理對象的建立。
JDK動態代理的代理類字節碼在建立時,須要實現業務實現類所實現的接口做爲參數。若是業務實現類是沒有實現接口而是直接定義業務方法的話,就沒法使用JDK動態代理了。(JDK動態代理重要特色是代理接口) 而且,若是業務實現類中新增了接口中沒有的方法,這些方法是沒法被代理的(由於沒法被調用)。
動態代理只能對接口產生代理,不能對類產生代理
Cglib是針對類來實現代理的,他的原理是對代理的目標類生成一個子類,並覆蓋其中方法實現加強,由於底層是基於建立被代理類的一個子類,因此它避免了JDK動態代理類的缺陷。
但由於採用的是繼承,因此不能對final修飾的類進行代理。final修飾的類不可繼承。
cglib 是基於asm 字節修改技術。導入 cglib 會間接導入 asm, ant, ant-launcher 三個jar 包。
<!-- cglib 動態代理依賴 begin --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version></dependency><!-- cglib 動態代理依賴 stop -->
cglib是針對類來實現代理的,原理是對指定的業務類生成他的一個子類,並覆蓋其中的業務方法來實現代理。由於採用的是繼承,因此不能對final修飾的類進行代理。
package org.vincent.proxy.cglibproxy;/** * @Package: org.vincent.proxy.cglibproxy <br/> * @Description: Cglib 代理模式中 被代理的委託類 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-17:55 <br/> */public class Dog { public String call() { System.out.println("wang wang wang"); return "Dog .."; } }
package org.vincent.proxy.cglibproxy;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/** * @Package: org.vincent.proxy.cglibproxy <br/> * @Description: Cglib 方法攔截器,不用依賴被代理業務類的引用。 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-17:56 <br/> */public class CglibMethodInterceptor implements MethodInterceptor { /** * 用於生成 Cglib 動態代理類工具方法 * @param target 表明須要 被代理的 委託類的 Class 對象 * @return */ public Object CglibProxyGeneratory(Class target) { /** 建立cglib 代理類 start */ // 建立增強器,用來建立動態代理類 Enhancer enhancer = new Enhancer(); // 爲代理類指定須要代理的類,也便是父類 enhancer.setSuperclass(target); // 設置方法攔截器回調引用,對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實現intercept() 方法進行攔截 enhancer.setCallback(this); // 獲取動態代理類對象並返回 return enhancer.create(); /** 建立cglib 代理類 end */ } /** * 功能主要是在調用業務類方法以前 以後添加統計時間的方法邏輯. * intercept 由於 具備 MethodProxy proxy 參數的緣由 再也不須要代理類的引用對象了,直接經過proxy 對象訪問被代理對象的方法(這種方式更快)。 * 固然 也能夠經過反射機制,經過 method 引用實例 Object result = method.invoke(target, args); 形式反射調用被代理類方法, * target 實例表明被代理類對象引用, 初始化 CglibMethodInterceptor 時候被賦值 。可是Cglib不推薦使用這種方式 * @param obj 表明Cglib 生成的動態代理類 對象自己 * @param method 代理類中被攔截的接口方法 Method 實例 * @param args 接口方法參數 * @param proxy 用於調用父類真正的業務類方法。能夠直接調用被代理類接口方法 * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before"); MonitorUtil.start(); Object result = proxy.invokeSuper(obj, args); //Object result = method.invoke(target, args); System.out.println("after"); MonitorUtil.finish(method.getName()); return result; } }
package org.vincent.proxy.cglibproxy;/** * Created by PengRong on 2018/12/25. * 方法用時監控類,做爲一個切面 ,具備兩個方法 */public class MonitorUtil { private static ThreadLocal<Long> tl = new ThreadLocal<>(); public static void start() { tl.set(System.currentTimeMillis()); } /** * 結束時打印耗時 * @param methodName 方法名 */ public static void finish(String methodName) { long finishTime = System.currentTimeMillis(); System.out.println(methodName + "方法執行耗時" + (finishTime - tl.get()) + "ms"); } }
package org.vincent.proxy.cglibproxy;import net.sf.cglib.core.DebuggingClassWriter;import net.sf.cglib.proxy.Enhancer;import org.junit.Test;import java.lang.reflect.Field;import java.util.Properties;/** * @Package: org.vincent.proxy.cglibproxy <br/> * @Description: TODO <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-18:05 <br/> */public class CglibTest { @Test public void testCglib() throws Exception { System.out.println(System.getProperty("user.dir")); /** 開啓 保存cglib生成的動態代理類類文件*/ saveGeneratedCGlibProxyFiles(System.getProperty("user.dir")); /** 第一種方法: 建立cglib 代理類 start */ // 建立增強器,用來建立動態代理類 Enhancer enhancer = new Enhancer(); // 爲代理類指定須要代理的類,也便是父類 enhancer.setSuperclass(Dog.class); // new 一個新的方法攔截器 CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor(); // 設置方法攔截器回調引用,對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實現intercept() 方法進行攔截 enhancer.setCallback(cglibMethodInterceptor); // 獲取動態代理類對象並返回 Dog dog = (Dog) enhancer.create(); /** 建立cglib 代理類 end */ System.out.println(dog.call()); // 對於上面這幾步,能夠新增一個工具方法 放置在 CglibMethodInterceptor 裏面;也就有了第二種方法 // new 一個新的方法攔截器,該攔截器還順帶一個用於建立代理類的工具方法。看起來簡單不少 cglibMethodInterceptor = new CglibMethodInterceptor(); dog = (Dog) cglibMethodInterceptor.CglibProxyGeneratory(Dog.class); System.out.println(dog.call()); } /** * 設置保存Cglib代理生成的類文件。 * * @throws Exception */ public void saveGeneratedCGlibProxyFiles(String dir) throws Exception { Field field = System.class.getDeclaredField("props"); field.setAccessible(true); Properties props = (Properties) field.get(null); System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, dir);//dir爲保存文件路徑 props.put("net.sf.cglib.core.DebuggingClassWriter.traceEnabled", "true"); } }
靜態代理是經過在代碼中顯式編碼定義一個業務實現類的代理類,在代理類中對同名的業務方法進行包裝,用戶經過代理類調用被包裝過的業務方法;
JDK動態代理是經過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;
CGlib動態代理是經過繼承業務類,生成的動態代理類是業務類的子類,經過重寫業務方法進行代理;
靜態代理在編譯時產生class字節碼文件,能夠直接使用,效率高。動態代理必須實現InvocationHandler接口,經過invoke調用被委託類接口方法是經過反射方式,比較消耗系統性能,但能夠減小代理類的數量,使用更靈活。 cglib代理無需實現接口,經過生成類字節碼實現代理,比反射稍快,不存在性能問題,但cglib會繼承目標對象,須要重寫方法,因此目標對象不能爲final類。
AOP的源碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。兩種方法同時存在,各有優劣。 jdk動態代理是由java內部的反射機制來實現的,cglib動態代理底層則是藉助asm來實現的。 總的來講,反射機制在生成類的過程當中比較高效,執行時候經過反射調用委託類接口方法比較慢;而asm在生成類以後的相關代理類執行過程當中比較高效(能夠經過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。 還有一點必須注意:jdk動態代理的應用前提,必須是委託類基於統一的接口。若是沒有上述前提,jdk動態代理不能應用。 由此能夠看出,jdk動態代理有必定的侷限性,cglib這種第三方類庫實現的動態代理應用更加普遍,且在效率上更有優點。
實現AOP關鍵特色是定義好兩個角色 切點 和 切面 。 代理模式中被代理類 委託類處於切點角色,須要添加的其餘好比 校驗邏輯,事務,審計邏輯 屬於非功能實現邏輯經過 切面類定義的方法插入進去。
定義切面接口,完成將通用公共方法注入到被代理類接口調用處理中
package org.vincent.aop.dynamicproxy;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: 定義切面接口,切面接口定義了兩個切面方法,分別在切點接口方法執行前和執行後執行 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public interface IAspect { /** * 在切點接口方法執行以前執行 * @param args 切點參數列表 * @return */ boolean startTransaction(Object... args); /** * 在切點接口方法執行以後執行 */ void endTrasaction(); }
定義切面實現類
package org.vincent.aop.dynamicproxy;import java.util.Objects;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: 改類做爲AOP 模型中切面角色類, 實現切面接口,切面接口定義了兩個切面方法,分別在切點接口方法執行前和執行後執行 。 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public class CustomAspect implements IAspect { /** * 對參數 作判空處理 * @param args 切點參數列表 * @return */ @Override public boolean startTransaction(Object... args) { Objects.nonNull(args); boolean result = true; for (Object temp :args) { if (Objects.isNull(temp)){ result =false; break; } } return result; } public void endTrasaction() { System.out.println("I get datasource here and end transaction"); } }
定義切點角色接口 由於是基於JDK實現的Aop ,因此委託類須要基於接口實現。
package org.vincent.aop.dynamicproxy;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: AOP基於動態代理 實現 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public interface IUserService { void saveUser(String username, String password) throws Exception; }
委託類實現
package org.vincent.aop.dynamicproxy;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: UserService接口實現類UserServiceImpl 該類 做爲AOP中切點角色,切面定義的方法插入到切點的接口方法 執行前和執行後執行。 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public class UserServiceImpl implements IUserService{ @Override public void saveUser(String username, String password) throws Exception { System.out.println("save user[username=" + username + ",password=" + password + "]"); } }
JDK動態代理生成器工具類
能夠看到 generatorJDKProxy 方法入參只有兩個參數 一個切點接口引用,一個切面接口引用;在InvocationHandler 內部類中能夠完整看到切面類方法是怎麼影響切點代碼執行邏輯的。
package org.vincent.aop.dynamicproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: JDK動態代理類生成器 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-16:48 <br/> */public class JDKDynamicProxyGenerator { /** * @param targetPoint 須要被代理的委託類對象 * @param aspect 切面對象,該對象方法將在切點方法以前或以後執行 * @return */ public static Object generatorJDKProxy(IUserService targetPoint, final IAspect aspect) { return Proxy.newProxyInstance( /** * 委託類使用的類加載器 */ targetPoint.getClass().getClassLoader(), /** * 委託類實現的接口 */ targetPoint.getClass().getInterfaces(), /** * 生成的動態代理類關聯的 執行處理器,代理咱們的業務邏輯被生成的動態代理類回調 * 具體邏輯代碼執行,返回值爲方法執行結果, 在aop模型中,委託類的接口方法稱爲切點。 */ new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 執行切面方法,對入參進行校驗 boolean prepareAction = aspect.startTransaction(args); if (prepareAction){ // 具體邏輯代碼執行,返回值爲方法執行結果 Object result = method.invoke(targetPoint, args); aspect.endTrasaction(); return result; }else { throw new RuntimeException("args: "+ Arrays.toString(args)+"不能爲null "); } } }); } }
測試類
package org.vincent.aop;import org.junit.Test;import org.vincent.aop.dynamicproxy.CustomAspect;import org.vincent.aop.dynamicproxy.IUserService;import org.vincent.aop.dynamicproxy.JDKDynamicProxyGenerator;import org.vincent.aop.dynamicproxy.UserServiceImpl;/** * @Package: org.vincent <br/> * @Description: 基於動態代理類AOP測試案例 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-16:56 <br/> */public class testAopJDKProxy { @Test public void testJDKProxy() throws Exception { System.out.println("無代理前 調用方法 userService.saveUser 輸出......"); IUserService userService = new UserServiceImpl(); userService.saveUser("zby", "1234567890"); System.out.println("有代理後AOP 是怎麼樣的? Proxy......"); IUserService proxyUserService = (IUserService) JDKDynamicProxyGenerator.generatorJDKProxy(userService, new CustomAspect()); proxyUserService.saveUser("zby", "1234567890"); /** 製造異常,兩個入參都是null */ proxyUserService.saveUser(null, null); } }
定義切面接口
package org.vincent.aop.cglib;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: 定義切面接口,切面接口定義了兩個切面方法,分別在切點接口方法執行前和執行後執行 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public interface IAspect { /** * 在切點接口方法執行以前執行 */ void startTransaction(); /** * 在切點接口方法執行以後執行 */ void endTrasaction(); }
切面實現
package org.vincent.aop.cglib;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: 改類做爲AOP 模型中切面角色類, 實現切面接口,切面接口定義了兩個切面方法,分別在切點接口方法執行前和執行後執行 。 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public class CustomAspect implements IAspect { @Override public void startTransaction() { System.out.println("cglib. I get datasource here and start transaction"); } public void endTrasaction() { System.out.println("cglib I get datasource here and end transaction"); } }
Cglib 是基於類實現的動態代理即業務類只須要實現類便可,不用強制必須實現某個接口爲了突出這個優勢這裏沒有實現接口
package org.vincent.aop.cglib;/** * @Package: org.vincent.aop.dynamicproxy <br/> * @Description: 業務實現類UserServiceImpl 該類 做爲AOP中切點角色,切面定義的方法插入到切點的接口方法 執行前和執行後執行。 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26 <br/> */public class UserServiceImpl { public void saveUser(String username, String password) { System.out.println("cglib save user[username=" + username + ",password=" + password + "]"); } }
Cglib 動態代理生成器工具類
package org.vincent.aop.cglib;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/** * @Package: org.vincent.aop.cglib <br/> * @Description: 基於Cglib代理類生成器工具類 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-17:04 <br/> */public class CglibProxyGenerator { /** * @param target 須要被代理的委託類對象,Cglib須要繼承該類生成子類 * @param aspect 切面對象,改對象方法將在切點方法以前或以後執行 * @return */ public static Object generatorCglibProxy(final Object target, final IAspect aspect){ //3.1 new Enhancer Enhancer enhancer = new Enhancer(); //3.2 設置須要代理的父類 enhancer.setSuperclass(target.getClass()); //3.3 設置回調 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 執行切面方法 aspect.startTransaction(); // 具體邏輯代碼執行,返回值爲方法執行結果 Object result = methodProxy.invokeSuper(proxy, args); // 執行切面方法 aspect.endTrasaction(); // 返回方法執行結果 return result; } }); // 3.4 建立代理對象 return enhancer.create(); } }
測試類
package org.vincent.aop;import org.junit.Test;import org.vincent.aop.cglib.CglibProxyGenerator;import org.vincent.aop.cglib.CustomAspect;import org.vincent.aop.cglib.UserServiceImpl;/** * @Package: org.vincent <br/> * @Description: 基於動態代理類AOP測試案例 <br/> * @author: lenovo <br/> * @Company: PLCC <br/> * @Copyright: Copyright (c) 2019 <br/> * @Version: 1.0 <br/> * @Modified By: <br/> * @Created by lenovo on 2018/12/26-16:56 <br/> */public class testAopCglibKProxy { @Test public void testCglibProxy() { System.out.println("before Proxy......"); UserServiceImpl userService = new UserServiceImpl(); userService.saveUser("zby", "1234567890"); System.out.println("引入Cglib Proxy代理庫 後......"); UserServiceImpl proxyUserService = (UserServiceImpl) CglibProxyGenerator.generatorCglibProxy(userService, new CustomAspect()); proxyUserService.saveUser("zby", "1234567890"); } }
AspectJ 實現 AOP 效果
AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理則可分爲靜態代理和動態代理兩大類:
原生 AspectJ 不依賴Spring案例, 基於 AspectJ 的編譯時加強進行 AOP 它是在編譯期修改字節碼,加強功能;並不會生成新的代理類字節碼。
<!-- AspectJ begin--><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.2</version></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version></dependency><!-- AspectJ stop-->