Java的三種代理模式完整源碼分析

Java的三種代理模式&完整源碼分析

Java的三種代理模式&完整源碼分析

參考資料:

博客園-Java的三種代理模式

簡書-JDK動態代理-超詳細源碼分析

[博客園-WeakCache緩存的實現機制](https://www.cnblogs.com/liuyun1995/p/8144676.html)

靜態代理

靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類

  1. 可以做到在不修改目標對象的功能前提下,對目標功能擴展
  2. 缺點:
    1. 因為代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多
    2. 同時,一旦接口增加方法,目標對象與代理對象都要維護

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式

實現靜態代理的步驟

  1. 定義業務接口
  2. 被代理類實現業務接口
  3. 定義代理類并實現業務接口
  4. 最后便可通過客戶端進行調用(這里可以理解成程序的main方法里的內容)

定義接口 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動態代理的優勢是實現無侵入式的代碼擴展,也就是方法的增強;讓你可以在不用修改源碼的情況下,增強一些方法;在方法的前后你可以做你任何想做的事情(甚至不去執行這個方法就可以)

特點

  1. 在程序運行時,通過反射機制動態生成
  2. 動態代理類通常代理接口下的所有類
  3. 動態代理事先不知道要代理的是什么,只有在運行的時候才能確定
JDK動態代理
  1. 動態代理的調用處理程序必須事先InvocationHandler接口,及使用Proxy類中的newProxyInstance方法動態的創建代理類
  2. Java動態代理只能代理接口,要代理類需要使用第三方的CLIGB等類庫

問題

  1. 為什么JDK動態代理只能代理接口?
Proxy.java->ProxyClassFactory->apply();/** Verify that the Class object actually represents an* interface.*/
// interfaceClass 指的是 Proxy.newProxyInstance 中的 interfaces
if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
}

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次為:

  • ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces:目標對象實現的接口的類型,使用泛型方式確認類型
  • InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入

實現JDK動態代理的步驟

  1. 創建被代理的接口和類
  2. 創建InvocationHandler接口的實現類,在invoke方法中實現代理邏輯
  3. 通過Proxy的靜態方法newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h)創建一個代理對象
  4. 使用代理對象

定義接口 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();

// 這個代理類工廠是在Proxy類初始化proxyClassCache靜態變量時傳入的
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{// prefix for all proxy class names// 所有代理類的前綴,我們在debug的時候看到的JDK代理對象都是這樣的private static final String proxyClassNamePrefix = "$Proxy";// next number to use for generation of unique proxy class names// 用于生成代理類名字的計數器private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);// 驗證接口// 1. 驗證類加載器加載的對象接口是否是同一個// 2. 驗證類對象是否是一個接口// 3. 驗證此接口是否重復for (Class<?> intf : interfaces) {/** Verify that the class loader resolves the name of this* interface to the same Class object.*/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");}/** Verify that the Class object actually represents an* interface.*/if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** Verify that this interface is not a duplicate.*/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;/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*/// 記錄非公共代理接口的包,以便在同一個包中定義代理類。驗證所有非公共代理接口都在同一個包中。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 packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();// 代理類的全限定名稱:com.sun.proxy.$Proxy0String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Generate the specified proxy class.*/// 核心代碼,生成代理類的字節碼byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 把代理類加載到JVM中,至此代理類創建完成了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());}}
}

我們再看看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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276683.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276683.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276683.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

scatter函數_matplotlib.pyplot常用函數scatter講解大全(三)

前言這篇文章再來總結一個常用畫圖函數scatter-散點圖。參數常用參數示例import matplotlib.pyplot as plt import numpy as np#導入需要的包 datanp.random.multivariate_normal([0,1],[[1,0],[0,1]],200)#準備數據&#xff0c;二維正態分布plt.rcParams["axes.unicode_m…

如何徹底卸載MySQL

本文摘自&#xff1a;http://www.heiqu.com/show-64764-1.html 內容為&#xff1a; 由于安裝MySQL的時候&#xff0c;疏忽沒有選擇底層編碼方式&#xff0c;采用默認的ASCII的編碼格式&#xff0c;于是接二連三的中文轉換問題隨之而來&#xff0c;就想卸載了重新安裝MYSQL&…

vue-cli項目模板的一些思考

之前有個想法&#xff0c;就是要利用vue寫一套ui。然后當時也沒有搞清楚到底怎么寫。 幾經周轉吧&#xff0c;通過付費的方式在gitbook上面找到了答案。 找到答案之后再看我們正在開發的項目&#xff0c;看伙伴寫的代碼&#xff0c;突然發現完全可以按照寫ui組件庫的方式調整目…

flex基于svn協同開發

想做一個游戲&#xff0c;正好有人陪我做。于是想到用flex來協同開發。本來是想使用cvs&#xff0c;可是結果搗鼓了半天&#xff0c;也沒個結果——估計是最近沒怎么看電影&#xff0c;IQ降下來了。于是改用svn。 參考資料&#xff1a;http://www.flashmagazine.com/tutorials/…

cookie與session詳解

session與cookie是什么?session與cookie屬于一種會話控制技術.常用在身份識別&#xff0c;登錄驗證&#xff0c;數據傳輸等.舉個例子&#xff0c;就像我們去超市買東西結賬的時候&#xff0c;我們要拿出我們的會員卡才會獲取優惠.這時候&#xff0c;我們怎么識別這個會員卡真實…

c++萬能頭文件_初學Python,與C對比

?背景學了一學年的C的基礎&#xff0c;下學年開課Python&#xff0c;現在正在自學中...C也不是不學了&#xff0c;而是之前買了一本《CPrimer》在學校里&#xff0c;就準備先學一下Python&#xff0c;下學期利用自由時間接著學習C。這里分析了一下二者的優缺點&#xff0c;供大…

listen(int fd, int backlog)中的backlog含義

1. listen(int fd, int backlog)中的backlog不能限制連接數量??? http://bbs.chinaunix.net/viewthread.php?tid870564 backlog應該是未完成3次握手連接和已完成3次握手而未被accept的兩對列之和.不知道我說的對不? 如果要控制連接數量,是不是要自己編碼控制...下面的可以…

本地無法啟動MySQL服務,報的錯誤:1067,進程意外終止---解決

原文鏈接&#xff1a;http://blog.csdn.net/shenhonglei1234/article/details/5928873 在本地計算機無法啟動MYSQL服務錯誤1067進程意外終止 這種情況一般是my.ini文件配置出錯了 首先找到這個文件&#xff1a; 默認安裝路徑 C:/Program Files/MySQL/MySQL Server 5.1/my.ini …

一篇文章助你理解Python3中字符串編碼問題

前幾天給大家介紹了unicode編碼和utf-8編碼的理論知識&#xff0c;以及Python2中字符串編碼問題&#xff0c;沒來得及上車的小伙伴們可以戳這篇文章&#xff1a;淺談unicode編碼和utf-8編碼的關系和一篇文章助你理解Python2中字符串編碼問題。下面在Python3環境中進行代碼演示&…

Express框架開發知識點總結

express --viewpug myapp 以上語句在當前工作目錄中創建名為 myapp 的 Express 應用程序&#xff0c;采用的模板是jade。 以前還在糾結使用hbs模板引擎或者ejs模板&#xff0c;實際上只要將--view后面的pug換成hbs&#xff08;使用的是Handlebars模板引擎&#xff09;&#xff…

《高級軟件架構師講義》學習筆記5

第五章 設計模式與軟件架構設計 一、面向對象軟件架構設計思想 a) 面向對象范式 i. 面向對象范式的核心是“對象”的概念 ii. 所有的東西都聚焦于對象 iii. 圍繞對象-而非函數-組織代碼 b) 對象從不同視角觀察 i. 概念層&#xff1a;…

python 安裝xpath_Python網絡爬蟲(四)- XPath1.XPath2.XPath在python中的應用

目錄&#xff1a; 1.XPath XPath即為XML路徑語言&#xff08;XML Path Language&#xff09;&#xff0c;它是一種用來確定XML文檔中某部分位置的語言。它使用路徑表達式來選取 XML 文檔中的節點或節點集。節點是通過沿著路徑 (path) 或者步 (steps) 來選取的。 XPath語法 2.XP…

團隊升級

2019獨角獸企業重金招聘Python工程師標準>>> 轉載于:https://my.oschina.net/yulongblog/blog/2988702

設計模式簡介

深入理解面向對象 向下&#xff1a;深入理解三大面向對象機制 封裝&#xff0c;隱藏內部實現 繼承&#xff0c;復用現有代碼  多態&#xff0c;改寫對象行為 向上&#xff1a;深刻把握面向對象機制所帶來的抽象意義&#xff0c;理解如何使用這些機制來表達現實世界&#xff0c…

Css3: gradient背景漸變

Css3: gradient背景漸變 原文鏈接&#xff1a;http://kk073000.blog.163.com/blog/static/34826942012123111322691/ css3實現了背景漸變。 <gradient> [ <linear-gradient> | <radial-gradient> | <repeating-linear-gradient> | <repeating-r…

模板方法

重構的關鍵技法&#xff1a; 靜態 -> 動態 早綁定 -> 晚綁定 繼承 -> 組合 編譯時依賴 -> 運行時依賴 緊耦合 -> 松耦合 "組件協作"模式 現代軟件專業分工之后的第一個結果是“框架與應用程序的劃分”&#xff0c;“組件協作”模式通過晚期綁定&#…

聚類 python_python中實現k-means聚類算法詳解

算法優缺點&#xff1a; 優點&#xff1a;容易實現 缺點&#xff1a;可能收斂到局部最小值&#xff0c;在大規模數據集上收斂較慢 使用數據類型&#xff1a;數值型數據 算法思想 k-means算法實際上就是通過計算不同樣本間的距離來判斷他們的相近關系的&#xff0c;相近的就會放…

python筆試常見題

1、冒泡排序&#xff1a; 冒泡排序算是最基本的python算法了。也算python面試遇到問的最多的了。 如果是封裝成函數。代碼如下&#xff1a; 如果初始就一個字典。那么代碼為&#xff1a; 冒泡排序的本質就是兩兩比較。根據結果調換位置。最終達到一個排序的效果。 注&#xff1…

固定資產打開提示:上年度數據未結轉!

問題現象&#xff1a;固定資產打開提示&#xff1a;上年度數據未結轉&#xff01; 問題分析&#xff1a;服務器出問題后&#xff0c;數據庫UFSYSTEM丟失&#xff0c;重新建賬后年度數據覆蓋后出現的&#xff0c;那么問題應該出在UFSYSTEM庫UA_ACCOUNT_SUB表與年度庫Accinformat…

windows MySQL 5+ 服務手動安裝

MySQL 5 服務手動安裝的方法&#xff1a;運行cmd&#xff0c;進入mysql的安裝目錄&#xff1a; C:\Users\aministrator> D: D:\> cd MySQL Server 5.6\bin D:\MySQL Server 5.6\bin>在bin目錄中運行mysqld.exe -install命令&#xff0c;安裝不完成會有提示信息。#1、手…