Java的三种代理模式完整源码分析
Java的三種代理模式&完整源碼分析
參考資料:
博客園-Java的三種代理模式
簡書-JDK動態代理-超詳細源碼分析
[博客園-WeakCache緩存的實現機制](https://www.cnblogs.com/liuyun1995/p/8144676.html)
靜態代理
靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類
如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式
實現靜態代理的步驟
定義接口 UserInterface
public interface UserInterface {// 保存用戶信息void save(); }定義接口的實現類 UserService
public class UserService implements UserInterface {@Overridepublic void save() {System.out.println("[靜態代理] 保存用戶信息");} }定義靜態代理 UserProxy
public class UserProxy implements UserInterface {private UserInterface userInterface;public UserProxy(UserInterface userInterface) {this.userInterface = userInterface;}/*** save 代理方法*/@Overridepublic void save() {// 調用目標方法前處理System.out.println("[靜態代理] save 開始代理...");// 調用目標方法userInterface.save();// 調用目標方法后處理System.out.println("[靜態代理] save 結束代理...");} }測試客戶端
public class Client {public static void main(String[] args) {// 新建目標對象UserService userService = new UserService();// 創建目標對象的代理對象UserProxy userProxy = new UserProxy(userService);// 執行代理對象userProxy.save();} }動態代理
Java動態代理的優勢是實現無侵入式的代碼擴展,也就是方法的增強;讓你可以在不用修改源碼的情況下,增強一些方法;在方法的前后你可以做你任何想做的事情(甚至不去執行這個方法就可以)
特點
JDK動態代理
問題
注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次為:
- ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces:目標對象實現的接口的類型,使用泛型方式確認類型
- InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入
實現JDK動態代理的步驟
定義接口 UserInterface
public interface UserInterface {// 保存用戶信息void save();// 更新用戶信息void update(); }定義接口的實現類 UserService
public class UserService implements UserInterface {@Overridepublic void save() {System.out.println("[JDK動態代理] 保存用戶信息");}@Overridepublic void update() {System.out.println("[JDK動態代理] 更新用戶信息");} }定義代理工廠 ProxyFactory
public class ProxyFactory {// 維護的目標對象private Object target;private Class<?> clazz;public ProxyFactory(Object target, Class<?> clazz) {this.target = target;this.clazz = clazz;}// 獲取代理對象public Object getProxyObjectByClazz() {return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{clazz},(proxy, method, args) -> {System.out.println("[JDK動態代理] save 開始代理...");System.out.println("當前線程名稱:" + Thread.currentThread().getName());String className = method.getDeclaringClass().getName();System.out.println("目標對象類名稱:" + className);String methodName = method.getName();System.out.println("目標對象方法名:" + methodName);Class<?>[] parameterTypes = method.getParameterTypes();System.out.println("目標對象參數:" + parameterTypes);// 執行目標對象并獲取返回值/該方法后面不會執行 // Object returnValue = method.invoke(target, args);System.out.println("[JDK動態代理] save 結束代理...");return null;});}/*** 獲取代理對象** @return*/public Object getProxyObjectByTarget() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println("[JDK動態代理] save 開始代理...");System.out.println("當前線程名稱:" + Thread.currentThread().getName());String className = method.getDeclaringClass().getName();System.out.println("目標對象類名稱:" + className);String methodName = method.getName();System.out.println("目標對象方法名:" + methodName);Class<?>[] parameterTypes = method.getParameterTypes();System.out.println("目標對象參數:" + parameterTypes);// 執行目標對象并獲取返回值Object returnValue = method.invoke(target, args);System.out.println("[JDK動態代理] save 結束代理...");return returnValue;});} }測試客戶端
public class Client {public static void main(String[] args) {System.out.println("********************* 使用接口生成代理對象 *********************");System.out.println("當前線程:" + Thread.currentThread().getName());UserInterface proxy = (UserInterface) new ProxyFactory(null, UserInterface.class).getProxyObjectByClazz();System.out.println("代理對象類型:" + proxy.getClass());proxy.save();System.out.println("********************* 使用實現類生成代理對象 *********************");UserService userService = new UserService();System.out.println("目標對象類型:" + userService.getClass());UserInterface proxy2 = (UserInterface) new ProxyFactory(userService, null).getProxyObjectByTarget();System.out.println("代理對象類型:" + proxy2.getClass());proxy2.update();} }輸出結果
********************* 使用接口生成代理對象 ********************* 當前線程:main 代理對象類型:class com.sun.proxy.$Proxy0 [JDK動態代理] save 開始代理... 當前線程名稱:main 目標對象類名稱:com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserInterface 目標對象方法名:save 目標對象參數:[Ljava.lang.Class;@c038203 [JDK動態代理] save 結束代理... ********************* 使用實現類生成代理對象 ********************* 目標對象類型:class com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserService 代理對象類型:class com.sun.proxy.$Proxy0 [JDK動態代理] save 開始代理... 當前線程名稱:main 目標對象類名稱:com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserInterface 目標對象方法名:update 目標對象參數:[Ljava.lang.Class;@cb5822 [JDK動態代理] 更新用戶信息 [JDK動態代理] save 結束代理... 運行結果和靜態代理一樣,說明成功了。但是,我們注意到,我們并沒有像靜態代理那樣去自己定義一個代理類,并實例化代理對象。實際上,動態代理的代理對象是在內存中的,是JDK根據我們傳入的參數生成好的。那動態代理的代理類和代理對象是怎么產生的呢?重頭戲來了,且往下看JDK動態代理源碼分析
代理對象的入口
Proxy.java->newProxyInstance();// 1. 查找或生成指定的代理類(下面會詳細說明該部分內容) Class<?> cl = getProxyClass0(loader, intfs);// 2. 根據Class獲取構造器 final Constructor<?> cons = cl.getConstructor(constructorParams);// 3. 返回實例化的構造器 return cons.newInstance(new Object[]{h});詳細說說 getProxyClass0 這個方法
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {// 限定代理的接口不能超過65535個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// 如果給定加載程序定義的代理類實現// 給定的接口存在,這只會返回緩存的副本;// 否則,它將通過proxyclassfactory創建代理類return proxyClassCache.get(loader, interfaces); }說明一下上面提到的 proxyClassCache
// proxyClassCache變量是在Proxy.java中的靜態變量 // 一個靜態的 proxy class 緩存對象 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());/* 那就再探究一下 WeakCache 這個類 */ final class WeakCache<K, P, V> {// Reference引用隊列private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();// the key type is Object for supporting null key// 使用了二級緩存技術,key為一級緩存,value為二級緩存,key是Object類型是為了存儲nullprivate final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();// reverseMap記錄了所有代理類生成器是否可用, 這是為了實現緩存的過期機制private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();// 生成二級緩存key的工廠, 這里傳入的是KeyFactoryprivate final BiFunction<K, P, ?> subKeyFactory;// 生成二級緩存value的工廠, 這里傳入的是ProxyClassFactoryprivate final BiFunction<K, P, V> valueFactory;/*** Construct an instance of {@code WeakCache}** @param subKeyFactory a function mapping a pair of* {@code (key, parameter) -> sub-key}* @param valueFactory a function mapping a pair of* {@code (key, parameter) -> value}* @throws NullPointerException if {@code subKeyFactory} or* {@code valueFactory} is null.*/// 構造器,上面初始化proxyClassCache用到的public WeakCache(BiFunction<K, P, ?> subKeyFactory,BiFunction<K, P, V> valueFactory) {this.subKeyFactory = Objects.requireNonNull(subKeyFactory);this.valueFactory = Objects.requireNonNull(valueFactory);}/*** Look-up the value through the cache. This always evaluates the* {@code subKeyFactory} function and optionally evaluates* {@code valueFactory} function if there is no entry in the cache for given* pair of (key, subKey) or the entry has already been cleared.** @param key possibly null key* @param parameter parameter used together with key to create sub-key and* value (should not be null)* @return the cached value (never null)* @throws NullPointerException if {@code parameter} passed in or* {@code sub-key} calculated by* {@code subKeyFactory} or {@code value}* calculated by {@code valueFactory} is null.*/// 這個方法我們下面詳細講public V get(K key, P parameter) {...}... }上面的一個小插曲,現在繼續講 WeakCache.java 中的 get 方法
// K和P就是WeakCache定義中的泛型,key是類加載器,parameter是接口類數組 public V get(K key, P parameter) {// 驗證接口類數組不為空Objects.requireNonNull(parameter);// 清除無效的緩存expungeStaleEntries();// 將ClassLoader包裝成CacheKey, 作為一級緩存的keyObject cacheKey = CacheKey.valueOf(key, refQueue);// lazily install the 2nd level valuesMap for the particular cacheKey// 獲取二級緩存ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);// 如果緩存中沒有,向緩存中放入數據if (valuesMap == null) {// CAS方式put,如果不存在則放入,存在則不放入。放入后會返回null,沒有放入會返回當前的valueConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());// 如果oldValuesMap有值, 說明放入失敗,也說明已經存在了,會把 valuesMap 刷新回以前存在的值if (oldValuesMap != null) {valuesMap = oldValuesMap;}}// create subKey and retrieve the possible Supplier<V> stored by that// subKey from valuesMap// 根據代理類實現的接口數組來生成二級緩存key, 分為key0, key1, key2, keyxObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));// 根據subKey獲取到二級緩存的值Supplier<V> supplier = valuesMap.get(subKey);Factory factory = null;// 這個循環提供了輪詢機制, 如果條件為假就繼續重試直到條件為真為止while (true) {if (supplier != null) {// supplier might be a Factory or a CacheValue<V> instance// 在這里supplier可能是一個Factory也可能會是一個CacheValue// 在這里不作判斷, 而是在Supplier實現類的get方法里面進行驗證// 下面詳細講這個方法V value = supplier.get();if (value != null) {return value;}}// else no supplier in cache// or a supplier that returned null (could be a cleared CacheValue// or a Factory that wasn't successful in installing the CacheValue)// lazily construct a Factoryif (factory == null) {// 新建一個Factory實例作為subKey對應的值factory = new Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {// 到這里表明subKey沒有對應的值, 就將factory作為subKey的值放入supplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {// successfully installed Factory// 到這里表明成功將factory放入緩存supplier = factory;}// else retry with winning supplier} else { // 否則, 可能期間有其他線程修改了值, 那么就不再繼續給subKey賦值, 而是取出來直接用if (valuesMap.replace(subKey, supplier, factory)) {// successfully replaced// cleared CacheEntry / unsuccessful Factory// with our Factory// 成功將factory替換成新的值supplier = factory;} else {// retry with current supplier// 替換失敗, 繼續使用原先的值supplier = valuesMap.get(subKey);}}} }WeakCache的get方法并沒有用鎖進行同步,那它是怎樣實現線程安全的呢?因為它的所有會進行修改的成員變量都使用了ConcurrentMap,這個類是線程安全的。因此它將自身的線程安全委托給了ConcurrentMap, get方法盡可能的將同步代碼塊縮小,這樣可以有效提高WeakCache的性能。我們看到ClassLoader作為了一級緩存的key,這樣可以首先根據ClassLoader篩選一遍,因為不同ClassLoader加載的類是不同的。然后它用接口數組來生成二級緩存的key,這里它進行了一些優化,因為大部分類都是實現了一個或兩個接口,所以二級緩存key分為key0,key1,key2,keyX。key0到key2分別表示實現了0到2個接口,keyX表示實現了3個或以上的接口,事實上大部分都只會用到key1和key2。這些key的生成工廠是在Proxy類中,通過WeakCache的構造器將key工廠傳入。這里的二級緩存的值是一個Factory實例,最終代理類的值是通過Factory這個工廠來獲得的
再詳細講 supplier.get()
@Override public synchronized V get() { // serialize access// re-check// 從二級緩存里面再獲取Supplier, 用來驗證是否是Factory本身Supplier<V> supplier = valuesMap.get(subKey);if (supplier != this) {// something changed while we were waiting:// might be that we were replaced by a CacheValue// or were removed because of failure ->// return null to signal WeakCache.get() to retry// the loop// 在這里驗證supplier是否是Factory實例本身, 如果不則返回null讓調用者繼續輪詢重試// 期間supplier可能替換成了CacheValue, 或者由于生成代理類失敗被從二級緩存中移除了return null;}// else still us (supplier == this)// create new valueV value = null;try {// 委托valueFactory去生成代理類, 這里會通過傳入的ProxyClassFactory去生成代理類// 后面詳細講 ProxyClassFactory 代理類工廠,代理對象就是在這里產生的value = Objects.requireNonNull(valueFactory.apply(key, parameter));} finally {if (value == null) { // remove us on failure// 如果生成代理類失敗, 就將這個二級緩存刪除valuesMap.remove(subKey, this);}}// the only path to reach here is with non-null value// 只有value的值不為空才能到達這里assert value != null;// wrap value with CacheValue (WeakReference)// 使用弱引用包裝生成的代理類CacheValue<V> cacheValue = new CacheValue<>(value);// put into reverseMap// 將cacheValue成功放入二級緩存后, 再對它進行標記reverseMap.put(cacheValue, Boolean.TRUE);// try replacing us with CacheValue (this should always succeed)// 用緩存包裝類替換this,必須成功,否則拋出異常if (!valuesMap.replace(subKey, this, cacheValue)) {throw new AssertionError("Should not reach here");}// successfully replaced us with new CacheValue -> return the value// wrapped by itreturn value; }最后一個核心方法 valueFactory.apply(key, parameter) 通過該方法就生成了代理類字節碼
Proxy.java->ProxyClassFactory->apply();
我們再看看Factory這個內部工廠類,可以看到它的get方法是使用synchronized關鍵字進行了同步。進行get方法后首先會去驗證subKey對應的suppiler是否是工廠本身,如果不是就返回null,而WeakCache的get方法會繼續進行重試。如果確實是工廠本身,那么就會委托ProxyClassFactory生成代理類,ProxyClassFactory是在構造WeakCache的時候傳入的。所以這里解釋了為什么最后會調用到Proxy的ProxyClassFactory這個內部工廠來生成代理類。生成代理類后使用弱引用進行包裝并放入reverseMap中,最后會返回原裝的代理類
探究代理類長什么樣
上面把JDK動態代理的過程分析完了,但是我這探究的心里還是有一道過不去的坎,動態代理存在什么地方了?跟我們直接實現的類有什么區別呢?下面繼續研究解答這兩個問題
通過下面的main方法就可以輸出到磁盤代理對象
public class GenerateClient {public static void main(String[] args) {// 在main方法最前面增加該行代碼,這樣會輸出代理class文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");UserService userService = new UserService();UserInterface proxy = (UserInterface) new ProxyFactory(userService, null).getProxyObjectByTarget();proxy.update();} }通過上面的代碼就可以生成動態代理class文件了,在我們項目路徑下com/sun/proxy/$Proxy0.class,我的項目路徑是 D:/workspace-mine/spring_boot,那我都文件就在這個地址下 D:/workspace-mine/spring_boot/com/sun/proxy/$Proxy0.class
上面這種方式可以保存到磁盤上,但是在JVM中代理對象是保存在內存中的,我們看不到
代理對象的樣子
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //package com.sun.proxy;import com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserInterface; 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 UserInterface {private static Method m1;private static Method m4;private static Method m2;private static Method m0;private static Method m3;// 代理類的構造函數,其參數正是是InvocationHandler實例,// Proxy.newInstance方法就是通過通過這個構造函數來創建代理實例的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 void save() throws {try {super.h.invoke(this, m4, (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);}}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);}}public final void update() throws {try {super.h.invoke(this, m3, (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"));m4 = Class.forName("com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserInterface").getMethod("save");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");m3 = Class.forName("com.example.spring_boot.modules.study.proxyobject.jdkproxy.UserInterface").getMethod("update");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}} }可以看到上面重寫了toString、equals、hashCode三個方法,生成了5個方法變量,分別指向各自的方法,其中m4和m3是接口中的save和update方法
上面重要的一個是構造函數$Proxy0,這個構造函數在代理對象還沒有生成前是不起做用的,直到代理對象生成了,這個構造器里面的參數就是我們在Proxy.newProxyInstance中傳入的new InvocationHandler(){...}這樣就開始執行我們構造器的方法了
如果想要在代理對象中執行代理方法可以直接這樣寫(main方法放在代理對象后面,需要把代理class轉成java)
public static void main(String[] args) {UserService userService = new UserService();$Proxy0 proxyObject = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("在代理對象中執行main方法開始...");method.invoke(userService, args);System.out.println("在代理對象中執行main方法結束...");return null;}});proxyObject.save(); }Cglib動態代理
敬請期待...
posted on 2019-09-23 17:13?小猴子先生 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/guoyinli/p/11573430.html
總結
以上是生活随笔為你收集整理的Java的三种代理模式完整源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux学习笔记(详细)
- 下一篇: java 运行异常处理_Java编程异常