代理模式java
代理模式是設計模式之一,爲一個對象提供一個替身或者佔位符以控制對這個對象的訪問,它給目標對象提供一個代理對象,由代理對象控制對目標對象的訪問。程序員
那麼爲何要使用代理模式呢?編程
一、隔離,客戶端類不能或者不想直接訪問目標對象,代理類能夠在遠程客戶端類和目標類之間充當中介。設計模式
2.代理類能夠對業務或者一些消息進行預處理,作一些過濾,而後再將消息轉給目標類,主要處理邏輯仍是在目標類,符合開閉原則。緩存
在咱們生活中有不少體現代理模式的例子,如中介、媒婆、經紀人等等,好比說某個球隊要簽約一個球星,就須要和經紀人進行溝通,在一些編程框架中,也有不少地方使用代理模式,如Spring的AOP,java的RMI遠程調用框架等。app
代理模式分爲靜態代理和動態代理,動態代理又分爲jdk動態代理和cglib動態代理,下面分別來闡述下這幾種代理模式的區別。框架
靜態代理ide
靜態代理在使用的時候,須要定義一個接口或者父類,代理類和目標類(被代理類)都須要實現這個接口,代理類持有目標類的引用。咱們以球員簽約爲例,湖人想要簽下安東尼戴維斯,安東尼說,先和個人經紀人商討簽約狀況,商談成功以後再來找我簽約。咱們定義一個會談的接口,這個接口提供一個簽約的方法,再定義一個經紀人類和球員類,分別實現會談接口的簽約方法。經紀人和湖人說,想要簽下戴維斯也能夠,不過咱們須要交易否決權,且最後一年是球員選項。湖人的魔術師想了想,詹姆斯巔峯期的尾巴也沒幾年了,反正這幾年垃圾合同也簽了很多,不在意這一個,並且戴維斯正值巔峯,聯盟前十球員,不算太虧,就贊成了,畢竟仍是總冠軍重要。因此,經紀人在正式簽約前,談妥了戴維斯的球員選項和薪水,剩下的就須要戴維斯本身親自簽約了。而後經紀人就拉着戴維斯來簽約了。(寫博客的期間,魔術師辭職了,我........,算了,懶得改了)函數
/** * 會談接口,有一個簽約的方法 */ public interface Talk { public void sign(); } /** * 球員安東尼戴維斯,實現了簽約的方法,須要本人親自簽約 */ public class Davis implements Talk { @Override public void sign() { System.out.println("簽約了,5年2.25億美圓"); } } /** * 經紀人,也實現了簽約的方法,持有球員的引用,可是具體簽約流程仍是必須由球員完成 */ public class Broker implements Talk { private Davis davis; public Broker(Davis davis){ this.davis = davis; } @Override public void sign() { System.out.println("咱們擁有最後一年的球員選項"); davis.sign(); System.out.println("簽約成功,交易否決權開始生效"); } } //** * 測試類,只須要調用經紀人的簽約方法就能夠了 */ public class StaticProxyTest { public static void main(String[] args) { Davis davis = new Davis(); Broker broker = new Broker(davis); broker.sign(); } } 控制檯打印: 咱們擁有最後一年的球員選項 簽約了,5年2.25億美圓 簽約成功,交易否決權開始生效
經過以上代碼,咱們發現,代理對象須要與目標對象實現同樣的接口,當須要代理的對象不少的時候,就須要增長不少的類,假如代理接口須要新增一個方法,那麼代理類和目標類都須要修改維護,那麼有沒有更好的解決方式呢?測試
動態代理
1、jdk動態代理
靜態代理的代理類是咱們在編譯期就已經建立好了,而動態代理是是指代理類是程序在運行過程當中建立的。jdk的動態代理是是基於接口的代理。
第一步:仍是定義一個會談接口:
/** * 會談接口,有一個簽約的方法 */ public interface Talk { public void sign(); }
第二步:定義一個球員,須要實現真正的簽約方法
/** * 球員安東尼戴維斯,實現了簽約的方法,須要本人親自簽約 */ public class Davis implements Talk { @Override public void sign() { System.out.println("簽約了,5年2.25億美圓"); } }
第三步:定義一個實現了InvocationHandler接口的實現類,該類須要綁定目標類。
public class MyInvocationHandler implements InvocationHandler { //目標類(被代理的類) private Object target; public MyInvocationHandler(Object target){ this.target = target; } /** * * @param proxy 生成的代理類的實例 * @param method 被調用的方法對象 * @param args 調用method方法時傳的參數 * @return method方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; System.out.println("咱們擁有最後一年的球員選項"); //執行方法,至關於執行了Davis中的sign()方法, //當代理對象調用其方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用 object = method.invoke(target,args); System.out.println("簽約成功,交易否決權開始生效"); return object; } }
第四步:編寫測試類:
public class DynamicProxyTest { public static void main(String[] args) { //被代理的對象 Talk talk = new Davis(); MyInvocationHandler myInvocationHandler = new MyInvocationHandler(talk); //傳入代理對象的字節碼文件和接口類型,讓Proxy來生成代理類 Talk davisProxy = (Talk)Proxy.newProxyInstance(talk.getClass().getClassLoader(), talk.getClass().getInterfaces(),myInvocationHandler); //調用簽約方法 davisProxy.sign(); } }
控制檯打印:
咱們擁有最後一年的球員選項
簽約了,5年2.25億美圓
簽約成功,交易否決權開始生效
能夠看到控制檯和靜態代理打印的如出一轍。下面來分析一下jdk動態代理的原理:
咱們看到,客戶端經過Proxy的靜態方法newProxyInstance生成了代理類,該方法有三個參數,分別是代理類的類加載器,這個代理類須要實現的接口以及一個處理器InvocationHandler,當咱們使用代理類調用sign()方法的時候,是怎麼執行這個方法的前置操做和後置操做,從代碼來看,咱們並無顯示的調用MyInvocationHandler的invoke方法,可是這個方法確實被執行了,到底是哪裏調用的呢?咱們來看一下newProxyInstance的源碼:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); 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. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
源碼有一些檢驗判斷,咱們暫且忽略,重點是看怎麼建立代理類以及代理類調用方法的時候如何調用的invoke方法的
重點剖析這行代碼:
Class<?> cl = getProxyClass0(loader, intfs);
這個方法根據傳入的類加載器和接口類型生成了一個類,這個類便是代理類。點進去:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
英文註釋寫到:若是緩存中有代理類了直接返回,不然將由ProxyClassFactory建立代理類。咱們再來看一下ProxyClassFactory:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 全部代理類的前綴名都以$Proxy開頭 private static final String proxyClassNamePrefix = "$Proxy"; // 下一個用於生成惟一代理類名的數字 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* *驗證該Class對象是否是接口 */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* *驗證此接口不是重複的 */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * 記錄非公共代理接口的包,以便代理類將在同一個包中定義 * 驗證全部非公共代理接口都在同一個包中 */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * 爲代理類選擇一個全限定類名 */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * 生成代理類的字節碼文件 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
咱們看到,最終調用了ProxyGenerator的generateProxyClass方法生成字節碼文件。回到newProxyInstance方法中,咱們看看這幾行代碼:
//代理類構造函數的參數類型 一、private static final Class<?>[] constructorParams = { InvocationHandler.class }; 二、final Constructor<?> cons = cl.getConstructor(constructorParams); 三、 cons.newInstance(new Object[]{h});
代理類實例化的代碼在第三行,經過反射調用代理類對象的構造方法,選擇了這個InvocationHandler爲參數的構造方法,這個h就是咱們傳遞過來的實現了InvocationHandler的實例。因此,咱們猜想是生成的代理類持有咱們前文定義的MyInvocationHandler實例,並調用裏面的invoke方法。因此,咱們經過反編譯來看下生成的代理類的源碼。
使用IDEA,在VM options一欄中輸入:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就會在項目中生成一個代理類:
package com.sun.proxy; import chenhuan.designpattern.proxy.staticproxy.Talk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; //該類繼承了Proxy類,實現了Talk接口 public final class $Proxy0 extends Proxy implements Talk { //聲明瞭一些Method變量,後面會用到 private static Method m1; private static Method m2; private static Method m3; 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}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } 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); } } //實現sign()方法,注意傳入的是m3, public final void sign() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); //加載Talk接口,並獲取其sign方法 m3 = Class.forName("chenhuan.designpattern.proxy.staticproxy.Talk").getMethod("sign"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
至此,咱們發現,代理類經過調super.h.invoke(this, m2, (Object[])null);執行咱們實現的InvocationHandler接口的invoke方法。覆盤下jdk動態代理的實現原理的幾大步驟:
一、新建一個接口
二、爲接口實現一個代理類
三、建立一個實現了InvocationHandler接口的處理器
四、經過Proxy的靜態方法,根據類加載器,實現的接口,以及InvocationHandler處理器生成一個代理類
①爲接口建立代理類的字節碼文件
②使用ClassLoader將字節碼文件加載到JVM
③建立代理類實例對象,執行對象的目標方法
咱們看到,使用JDK動態代理,目標類必須實現的某個接口,若是某個類沒有實現接口則不能生成代理對象,那麼,有沒有不須要目標類實現接口的動態代理呢?讓咱們來看下cglib動態代理。
2、cglib動態代理
使用cglib須要引入cglib jar包,本篇案例使用的是cglib2.2.jar
直接先來看下代碼實現:
一、先定義一個目標類
package chenhuan.designpattern.proxy; public class Davis { public void sign() { System.out.println("簽約了,5年2.25億美圓"); } }
二、定義一個攔截器,
public class MyMethodInterceptor implements MethodInterceptor { /** * * @param o 表示加強的對象,即實現這個接口類的一個對象 * @param method 表示要被攔截的方法 * @param objects 表示要被攔截方法的參數 * @param methodProxy * @return 表示要觸發父類的方法對象 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("咱們擁有最後一年的球員選項");
//調用代理類實例上的proxy方法的父類方法 Object object = methodProxy.invokeSuper(o,objects); System.out.println("簽約成功,交易否決權開始生效"); return object; } }
三、使用字節碼加強器來生成代理類:
public class CglibProxyTest { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); //設置enhancer的父類對象 enhancer.setSuperclass(Davis.class); //設置enhancer的回調對象,就是咱們定義的攔截器 enhancer.setCallback(new MyMethodInterceptor()); //生成代理類 Davis davis = (Davis)enhancer.create(); davis.sign(); } }
cglib底層採用ASM字節碼生成框架,使用字節碼技術生成代理類,比使用Java反射效率要高。須要注意的是,cglib不能對聲明爲final的方法進行代理,由於CGLib原理是動態生成被代理類的子類。
本文感受篇幅過長,就不分析cglib動態代理的源碼了 。
總結:靜態代理由程序員建立代理類,在程序運行前代理類就已經存在了,而且代理類和目標類都要實現相同的接口。當須要代理的對象不少的時候,就須要增長不少的類,假如代理接口須要新增一個方法,那麼代理類和目標類都須要修改維護,不易維護。jdk動態代理須要目標類實現接口,也就是說jdk動態代理只能對該類中實現了目標接口的方法進行代理,這個在實際編程中可能存在侷限性,cglib動態代理徹底不受代理類必須實現接口的限制,其生成的代理類是目標類的子類。