代理模式最大的優勢就是能夠解耦,在spring中也是廣泛使用。spring中一個重要的特性就是aop,aop是個啥東西呢?其實很簡單,比如現在有個業務方法,那這個業務方法很重要,涉及到非常重要的業務數據,那對於廣大企業應用來說,為了以后能夠及時的定位問題,需要記錄相關入參以及出參到日志表。
但是對於企業應用來說,需要記錄日志的地方應該是蠻多的,如果每個方法中都手動的去寫這些記錄日志的東西,就會特別的冗余,那使用代理模式就可以解決。
一、靜態代理
1、User接口
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下沒有難敲的代碼! */ public interface User { /* * 業務邏輯接口 */ public void work(String workName); }
2、UserImpl實現類
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下沒有難敲的代碼! */ public class UserImpl implements User { /* * 實際業務邏輯實現方法 */ @Override public void work(String workName) { System.out.println("我是做" + workName + "的"); } }
3、代理類
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下沒有難敲的代碼! * 此類為代理類,並且實現業務邏輯類接口,接收一個實際業務處理對象 */ public class ProxyUser implements User { private User user; public ProxyUser(User user) { this.user = user; } @Override public void work(String workName) { /* * 調用實際業務邏輯處理前可以定制化一些功能 */ System.out.println("工作前先放松放松============="); /* * 調用實際業務邏輯處理方法 */ user.work(workName); /* * 調用實際業務邏輯處理后也可以定制一些功能 */ System.out.println("工作后還是要放松放松============="); } }
4、StaticProxyDemo
package com.ty.staticProxy; /** * @author Taoyong * @date 2018年7月1日 * 天下沒有難敲的代碼! */ public class StaticProxyDemo { public static void main(String[] args) { User proxyUser = new ProxyUser(new UserImpl()); proxyUser.work("java開發"); } }
運行結果:
工作前先放松放松=============
我是做java開發的
工作后還是要放松放松=============
優點:
- 1、 代理模式能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。
- 2、 代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了的作用和保護了目標對象的
缺點:
- 1、由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
- 2、 實現代理模式需要額外的工作,有些代理模式的實現非常復雜。使用靜態代理模式需要程序員手寫很多代碼。
二、動態代理
在java中,實現動態代理主要有兩種方式,一種是jdk動態代理,一種是cglib
1、jdk動態代理
User以及UserImpl跟上面一致
a、UserDynamicProxy
package com.ty.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author Taoyong * @date 2018年7月2日 * 天下沒有難敲的代碼! */ public class UserDynamicProxy implements InvocationHandler { private User user; public UserDynamicProxy(User user) { this.user = user; } /* * jdk動態代理基於接口,其中proxy好像沒啥卵用、method代表當前被代理對象的實際調用方法、args則代表方法參數 * 由invoke方法對被代理對象進行相關的增強 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("工作前放松放松========"); method.invoke(user, args); System.out.println("工作后也要放松放松==========="); return null; } }
b、DynamicProxyDemo
package com.ty.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author Taoyong * @date 2018年7月2日 * 天下沒有難敲的代碼! */ public class DynamicProxyDemo { public static void main(String[] args) { User user = new UserImpl(); InvocationHandler h = new UserDynamicProxy(user); /* * 使用Proxy的靜態方法是生成代理類的核心。 * 一共有三個參數: * 1、第一個參數是被代理類的類加載器,通過此類加載器將代理類加載入jvm中; * 2、第二個參數則是被代理類所實現的所有接口,需要所有的接口的目的是創建新的代理類實現被代理類的所有接口,保證被代理類所有方法都能夠 * 被代理。其實代理的核心就是新創建一個類並實例化對象,去集成被代理對象所有功能的同時,再加入某些特性化的功能; * 3、第三個參數則是真正的擴展,使用動態代理的主要目的就是能夠對原方法進行擴展,尤其是對於大部分方法都具有的重復方法(例如記錄日志), * 可以理解為面向切面編程中的增強. */ User proxy = (User) Proxy.newProxyInstance(User.class.getClassLoader(), user.getClass().getInterfaces(), h); /* * 在調用生成的代理類對象后,調用原方法后,該method對象以及參數等會被傳入到InvocationHandler的invoke方法中,由InvocationHandler的 * invoke方法對被代理類對象進行增強。 */ proxy.work("敲代碼"); proxy.eat("吃大餐"); } }
2、cglib代理
jdk動態代理的缺點就是必須基於接口,沒有接口就無法實現代理,而cglib則是使用繼承的方式去生成代理類,使用范圍更廣
a、添加cglib.jar、asm.jar(注意jar包版本)
使用cglib前必須進行導包,並且版本如果過低會導致報錯
b、UserImpl(使不使用接口都可以)
package com.ty.dynamic.cglib; /** * @author Taoyong * @date 2018年7月1日 * 天下沒有難敲的代碼! */ public class UserImpl { /* * 實際業務邏輯實現方法 */ public void work(String workName) { System.out.println("我是做" + workName + "的"); } }
c、CglibDynamicProxy
package com.ty.dynamic.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * @author Taoyong * @date 2018年7月2日 * 天下沒有難敲的代碼! */ public class CglibDynamicProxy implements MethodInterceptor { /* * 實際的增強 */ @Override public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("這是前處理=============="); methodProxy.invokeSuper(obj, objects); System.out.println("這是前處理=============="); return null; } }
d、CglibProxyDemo
package com.ty.dynamic.cglib; import net.sf.cglib.proxy.Enhancer; /** * @author Taoyong * @date 2018年7月2日 * 天下沒有難敲的代碼! */ public class CglibProxyDemo { public static void main(String[] args) { /* * 創建字節碼增強器 */ Enhancer enhancer = new Enhancer(); /* * 被代理類設置為字節碼增強器父類,cglib使用的是繼承方式去創建代理類 */ enhancer.setSuperclass(UserImpl.class); /* * 設置字節碼增強器回調方法。對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔截
*/
enhancer.setCallback(new CglibDynamicProxy());
/*
* 創建代理實例
*/
UserImpl userImpl = (UserImpl) enhancer.create(); userImpl.work("敲代碼"); } }
3、原理-----基於jdk動態代理
上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因為所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。上述代碼里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再沒有任何特殊之處。
在JDK動態代理中涉及如下角色:
業務接口Interface、業務實現類target、業務處理類Handler、JVM在內存中生成的動態代理類$Proxy0
動態代理原理圖:
說白了,動態代理的過程是這樣的:
-
Proxy通過傳遞給它的參數(interfaces/invocationHandler)生成代理類$Proxy0;
-
Proxy通過傳遞給它的參數(ClassLoader)來加載生成的代理類$Proxy0的字節碼文件;
動態代理的關鍵代碼就是Proxy.newProxyInstance(classLoader, interfaces, handler),我們跟進源代碼看看
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // handler不能為空 if (h == null) { throw new NullPointerException(); } final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ // 通過loader和接口,得到代理的Class對象 Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { // 創建代理對象的實例 return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
我們看一下newInstance方法的源代碼:
private static Object newInstance(Constructor<?> cons, InvocationHandler h) { try { return cons.newInstance(new Object[] {h} ); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString()); } } }
講解完了代理類的生成源碼,我們一定想要看看代理類的代碼是什么樣的,下面提供一個生成代理類的方法供大家使用:
/** * 代理類的生成工具 * @author ChenHao * @since 2019-4-2 */ public class ProxyGeneratorUtils { /** * 把代理類的字節碼寫到硬盤上 * @param path 保存路徑 */ public static void writeProxyClassToHardDisk(String path) { // 第一種方法 // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true); // 第二種方法 // 獲取代理類的字節碼 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class"); } }
此時就會在指定的C盤x文件夾下生成代理類的.class文件,我們看下反編譯后的結果:
package org.fenixsoft.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello { private static Method m3; private static Method m1; private static Method m0; private static Method m2; /** *注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白 *super(paramInvocationHandler),是調用父類Proxy的構造方法。 *父類持有:protected InvocationHandler h; *Proxy構造方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } /** * *這里調用代理對象的sayHello方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。 *this.h.invoke(this, m3, null); this.h就是父類Proxy中保存的InvocationHandler實例變量 *來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象, *再聯系到InvacationHandler中的invoke方法。嗯,就是這樣。 */ public final void sayHello() throws { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } // 此處由於版面原因,省略equals()、hashCode()、toString()三個方法的代碼 // 這3個方法的內容與sayHello()非常相似。 static { try { m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
java.lang.reflect.Proxy
public class Proxy implements java.io.Serializable { protected InvocationHandler h; private Proxy() { } protected Proxy(InvocationHandler h) { doNewInstanceCheck(); this.h = h; } //略 }
這個代理類的實現代碼也很簡單,它為傳入接口中的每一個方法,以及從 java.lang.Object中繼承來的equals()、hashCode()、toString()方法都生成了對應的實現 ,並且統一調用了InvocationHandler對象的invoke()方法(代碼中的“this.h”就是父類Proxy中保存的InvocationHandler實例變量)來實現這些方法的內容,各個方法的區別不過是傳入的參數和Method對象有所不同而已,所以無論調用動態代理的哪一個方法,實際上都是在執行InvocationHandler.invoke()中的代理邏輯。