設計模式 - 動態代理原理及模仿JDK Proxy 寫一個屬於自己的動態代理
本篇文章程式碼內容較多,講的可能會有些粗糙,大家可以選擇性閱讀。
本篇文章的目的是簡單的分析動態代理的原理及模仿
JDK Proxy
手寫一個動態代理以及對幾種代理做一個總結。
對於代理模式的介紹和講解,網上已經有很多優質的文章,我這裡就不會再過多的介紹了,這裡推薦幾篇優質的文章作為參考:
另外,我的 github 倉庫對應目錄中也有相關的基礎示例程式碼:github.com/eamonzzz/ja…
JDK Proxy 動態代理
動態代理的概念這裡就不再闡述了;動態代理相對於靜態代理來說,它的功能更加強大,隨著業務的擴充套件,適應性更強。
在說動態代理原理之前,我們還是來看看動態代理的一般使用。
使用
本篇文章的使用示例,是以一個最為簡單的代理模式的程式碼為例,相信大家在學習或瞭解代理模式的時候都有看到或者接觸過這些程式碼。
- 先建立一個
Subject
主體抽象介面:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:06
*/
public interface Subject {
void request();
}複製程式碼
- 再建立一個真實的主體
RealSubject
來處理我們的真實的邏輯:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:06
*/
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真實處理邏輯!");
}
}複製程式碼
- 在不修改
RealSubject
類的情況下,如果我們要實現在執行RealSubject
類中request()
方法之前或之後執行一段邏輯的話,該怎麼實現呢?這就得建立一個代理類,來達到增強原有程式碼的目的。所以現在建立一個 JDK 動態代理類RealSubjectJDKDynamicProxy
:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:08
*/
public class RealSubjectJDKDynamicProxy implements InvocationHandler {
// 被代理物件的引用
private Object target;
// 通過構造器傳入物件引用
public RealSubjectJDKDynamicProxy(Object target) {
this.target = target;
}
// 獲得 JDK 動態代理建立的代理物件
public Object getInstance() {
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
@Override
public Object invoke(Object o,Method method,Object[] objects) throws Throwable {
before();
// 代理執行被代理物件的相應方法
Object invoke = method.invoke(target,objects);
after();
return invoke;
}
private void before() {
System.out.println("前置增強!");
}
private void after() {
System.out.println("後置增強!");
}
}複製程式碼
- 測試程式碼:
@Test
public void test(){
Subject realSubject = new RealSubject();
RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);
Subject instance = (Subject) proxy.getInstance();
instance.request();
System.out.println(realSubject.getClass());
System.out.println(instance.getClass());
}複製程式碼
- 測試結果
前置增強!
真實處理邏輯!
後置增強!
class com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject
class com.sun.proxy.$Proxy8複製程式碼
從結果來看,上面的程式碼已經達到了我們的增強的目的。
原理分析
不知道大家有沒有注意到上面的測試程式碼中,最後兩行我將代理之前和代理之後的class
物件給列印了出來;並且發現,這兩個物件並非同一個,最重要的是,經過代理之後的物件的Subject
是com.sun.proxy.$Proxy8
而不是com.eamon.javadesignpatterns.proxy.dynamic.jdk.RealSubject
或者com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject
,那麼這個instance
到底是從哪裡來?帶著這個疑問,我們來通過 JDK Proxy 原始碼來分析一下:
我們跟進RealSubjectJDKDynamicProxy
類中的Proxy.newProxyInstance(clazz.getClassLoader(),this);
方法:
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 {
...
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);
}
...
}複製程式碼
發現在newProxyInstance
方法中呼叫了getProxyClass0(loader,intfs)
方法,我們跟進去這個方法看一下:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
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);
}複製程式碼
程式碼邏輯很簡單,做了兩個事情:
- 檢查類的介面數量是否超過
65535
,介面個數用 2 個byte
儲存,最大支援65535
個。 - 從
proxyClassCache
快取中去取,從註釋中可知,如果快取沒有就會呼叫ProxyClassFactory
去建立。
我們現在就來簡單分析一下proxyClassCache.get(loader,interfaces)
裡面的邏輯:
public V get(K key,P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object 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) {
ConcurrentMap<Object,Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key,parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
// 這裡是一個 while(true)
while (true) {
// 如果建立 factory(這裡指ProxyClassFactory) 成功,就呼叫 factory.get()方法
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
//
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 Factory
if (factory == null) {
factory = new Factory(key,parameter,subKey,valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey,factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey,supplier,factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}複製程式碼
程式碼可能有點長,其實邏輯就是為了呼叫ProxyClassFactory.apply()
去生成代理類。我們從while(true)
處將程式碼分割成兩個部分來看:
- 前半部分,是從快取中去取
ProxyClassFactory
,如果建立成功了,則可以取到(快取中的 key 這裡不分析了) - 然後看
while(true)
程式碼塊中的邏輯,if (supplier != null)
這個判斷,如果快取中建立了ProxyClassFactory
就會執行supplier.get()
並且終止迴圈;如果沒有,則會執行new Factory(key,valuesMap);
去建立factory
,然後將其放入快取supplier
中,然後繼續迴圈,這個時候就會執行if (supplier != null)
程式碼塊中的邏輯,我們再來分析一下這個程式碼塊裡面的程式碼:
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}複製程式碼
跟進 supplier.get()
方法去看一下,我們從上面的分析可以知道這裡的supplier
其實就是一個Factory
,所以我們看Factory
的實現,重點看get()
方法:
private final class Factory implements Supplier<V> {
...
@Override
public synchronized V get() { // serialize access
...
// create new value
V value = null;
try {
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
assert value != null;
// wrap value with CacheValue (WeakReference)
CacheValue<V> cacheValue = new CacheValue<>(value);
// put into reverseMap
reverseMap.put(cacheValue,Boolean.TRUE);
// try replacing us with CacheValue (this should always succeed)
if (!valuesMap.replace(subKey,this,cacheValue)) {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}複製程式碼
我們注意到,程式碼中的重點是在Objects.requireNonNull(valueFactory.apply(key,parameter));
,那這個程式碼中的valueFactory
是什麼呢?我們在Proxy
中,來看一下proxyClassCache
的定義
private static final WeakCache<ClassLoader,Class<?>[],Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory());複製程式碼
WeakCache
中第二個引數是new ProxyClassFactory()
,再來看一下對應的構造器:
public WeakCache(BiFunction<K,P,?> subKeyFactory,BiFunction<K,V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}複製程式碼
這時候明白了嗎?其實 valueFactory
就是ProxyClassFactory()
明白了這一點,就來分析一下valueFactory.apply(key,parameter)
到底執行了什麼?我們直接看ProxyClassFactory
的程式碼
private static final class ProxyClassFactory
implements BiFunction<ClassLoader,Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader,Class<?>[] interfaces) {
...
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName,interfaces,accessFlags);
try {
return defineClass0(loader,proxyName,proxyClassFile,proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}複製程式碼
縱觀全覽,不難分析,程式碼中其實就是在建立$Proxy
這個中間代理類,其中byte[] proxyClassFile
是程式碼塊中組裝完成之後的類的位元組碼檔案資料,通過ProxyGenerator.generateProxyClass()
生成;然後通過classloader
動態載入位元組碼,並生成動態代理類的Class
例項,並返回。
我們再跟進ProxyGenerator.generateProxyClass()
方法,來看看在生成代理類過程中的處理邏輯,看重點程式碼:。
public static byte[] generateProxyClass(final String var0,Class<?>[] var1,int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0,var1,var2);
final byte[] var4 = var3.generateClassFile();
...
return var4;
}複製程式碼
可以發現其程式碼呼叫了var3.generateClassFile()
去生成Class
檔案,所以我們跟進generateClassFile()
方法,看重點內容:
private byte[] generateClassFile() {
this.addProxyMethod(hashCodeMethod,Object.class);
this.addProxyMethod(equalsMethod,Object.class);
this.addProxyMethod(toStringMethod,Object.class);
Class[] var1 = this.interfaces;
int var2 = var1.length;
int var3;
Class var4;
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
Method[] var5 = var4.getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
this.addProxyMethod(var8,var4);
}
}
...
}複製程式碼
程式碼有點長,這裡就不全部展開了,有興趣的朋友可以跟進去詳細看一下。從程式碼中我們大致可以看出來,在生成代理類的過程中,還添加了hashCode、equals、toString
這三個方法,然後後面的邏輯就是將代理物件中的所有介面進行迭代,將其所有的方法都重新生成代理方法;然後生成位元組碼。
最後再將代理類載入到JVM
中。
看一下JDK Proxy
生成的代理類$Proxy
我們通過下面這段程式碼,將$Proxy
檔案輸出到檔案:
@Test
public void test1(){
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
RealSubject realSubject = new RealSubject();
RealSubjectJDKDynamicProxy proxy = new RealSubjectJDKDynamicProxy(realSubject);
Subject instance = (Subject) proxy.getInstance();
try {
byte[] proxychar= ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Subject.class});
OutputStream outputStream = new FileOutputStream("/Users/eamon.zhang/IdeaProjects/own/java-advanced/01.DesignPatterns/design-patterns/"+instance.getClass().getSimpleName()+".class");
outputStream.write(proxychar);
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
instance.request();
System.out.println(instance.getClass());
}複製程式碼
通過IDEA
工具檢視$Proxy0
,印證一下我們之前的分析:
public final class $Proxy0 extends Proxy implements Subject {
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);
}
}
public final void request() 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");
m3 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.jdk.Subject").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}複製程式碼
總結
總結一下JDK Proxy
的實現步驟:
- 拿到被代理物件的引用,並獲取它的所有介面(通過反射)
-
JDK Proxy
類重新生成一個新的類,同時新的類要實現被代理類的所有實現的介面,還有hashCode、equals、toString
這三個方法 - 動態生成
Java
程式碼,把新加的業務邏輯方法由一定的邏輯程式碼去呼叫(在程式碼中體現) - 編譯新生成的
Java
程式碼的.class
檔案 - 重新載入到
JVM
中執行
模擬手寫 JDK Proxy
在明白了上面的原理之後,其實我們就可以嘗試手動來實現一個JDK Proxy
:
我們參照JDK Proxy
實現原理分析一下需要動手編寫哪些內容:
- 首先我們需要有一個代理類
MimeProxy
- 然後從代理類出發,需要有
newProxyInstance(clazz.getClassLoader(),this)
這一個方法,方法引數為:(ClassLoader loader,Class>[] interfaces,InvocationHandler h)
,所以我們需要建立一個ClassLoader
、InvocationHandler
;
下面來一步一步建立:
- 先建立
MimeClassLoader
類,繼承自ClassLoader
,並重寫findClass()
方法:
/**
* @author eamon.zhang
* @date 2019-10-10 下午2:47
*/
public class MimeClassLoader extends ClassLoader {
private Object target;
public MimeClassLoader(Object target) {
this.target = target;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classname = target.getClass().getPackage().getName() + "." + name;
String filePath = MimeClassLoader.class.getResource("").getPath() + name + ".class";
try {
URI uri = new URI("file:///" + filePath);
Path path = Paths.get(uri);
File file = path.toFile();
if (file.exists()) {
byte[] fileBytes = Files.readAllBytes(path);
return defineClass(classname,fileBytes,fileBytes.length);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}複製程式碼
- 建立
MimeInvocationHandler
類:
/**
* @author eamon.zhang
* @date 2019-10-10 下午2:46
*/
public interface MimeInvocationHandler {
public Object invoke(Object proxy,Object[] args)
throws Throwable;
}複製程式碼
- 建立
MimeProxy
類,這個類就是用來組裝成代理類,並載入到JVM
,然後返回這個代理物件:
/**
* @author eamon.zhang
* @date 2019-10-10 下午3:08
*/
public class MimeProxy {
private static final String ln = "\r\n";
private static final String semi = ";";
private static Map<Class,Class> mappings = new HashMap<Class,Class>();
static {
mappings.put(int.class,Integer.class);
}
public static Object newProxyInstance(MimeClassLoader loader,MimeInvocationHandler h)
throws IllegalArgumentException {
try {
// 1. 動態生成 .java 檔案
String src = generateSrc(interfaces);
// System.out.println(src);
// 2. java 檔案輸出到磁碟
String filePath = MimeProxy.class.getResource("").getPath();
// System.out.println(filePath);
File f = new File(filePath + "$Proxy8.java");
// f.deleteOnExit();
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
// 3. 把 java 檔案編譯成 .class 檔案
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = compiler.getStandardFileManager(null,null,null);
Iterable<? extends JavaFileObject> iterable = sjfm.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null,sjfm,iterable);
task.call();
sjfm.close();
// 4. 把.class 檔案載入到jvm
Class<?> proxyClass = loader.findClass("$Proxy8");
Constructor<?> c = proxyClass.getConstructor(MimeInvocationHandler.class);
f.delete();
// 5. 返回位元組碼重組以後的新的代理物件
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成 代理類
*
* @param interfaces
* @return
*/
private static String generateSrc(Class<?>[] interfaces) {
// 這裡使用 StringBuffer 執行緒安全
StringBuffer sb = new StringBuffer();
sb.append("package ").append(interfaces[0].getPackage().getName()).append(semi).append(ln);
sb.append("import ").append(interfaces[0].getName()).append(semi).append(ln);
sb.append("import java.lang.reflect.*;").append(ln);
sb.append("import ").append(interfaces[0].getPackage().getName()).append(".mimeproxy.MimeInvocationHandler;").append(ln);
sb.append("public class $Proxy8 implements ").append(interfaces[0].getSimpleName()).append(" {").append(ln);
sb.append("MimeInvocationHandler h;" + ln);
sb.append("public $Proxy8(MimeInvocationHandler h) {").append(ln);
sb.append("this.h = h;").append(ln);
sb.append("}").append(ln);
for (Method method : interfaces[0].getMethods()) {
Class<?>[] params = method.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (Class<?> clazz : params) {
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type).append(" ").append(paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName()).append(".class");
for (int i = 0; i < params.length; i++) {
paramNames.append(",");
paramValues.append(",");
paramClasses.append(",");
}
}
sb.append("public ").append(method.getReturnType().getName()).append(" ").append(method.getName())
.append("(").append(paramNames.toString()).append(") {").append(ln);
sb.append("try {").append(ln);
// Method m = interfaces[0].getName().class.getMethod(method.getName()),new Class[]{paramClasses.toString()});
sb.append("Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"")
.append(method.getName()).append("\",new Class[]{").append(paramClasses.toString()).append("});")
.append(ln);
// return this.h.invoke(this,m,new Object[]{paramValues},method.getReturnType());
sb.append(hasReturnValue(method.getReturnType()) ? "return " : "")
.append(getCaseCode("this.h.invoke(this,new Object[]{" + paramValues + "})",method.getReturnType()))
.append(";")
.append(ln);
sb.append("} catch (Error _ex) {}").append(ln);
sb.append("catch (Throwable e) {").append(ln);
sb.append("throw new UndeclaredThrowableException(e);").append(ln);
sb.append("}");
sb.append(getReturnEmptyCode(method.getReturnType())).append(ln);
sb.append("}");
}
sb.append("}").append(ln);
return sb.toString();
}
/**
* 獲取返回值型別
*
* @param returnClass
* @return
*/
private static String getReturnEmptyCode(Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "return 0;";
} else if (returnClass == void.class) {
return "";
} else {
return "return null;";
}
}
/**
* 拼接 invocationHandler 執行程式碼
*
* @param code
* @param returnClass
* @return
*/
private static String getCaseCode(String code,Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
/**
* 判斷是否有返回值
*
* @param clazz
* @return
*/
private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
/**
* 首字母轉換為小寫
*
* @param src
* @return
*/
private static String toLowerFirstCase(String src) {
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}複製程式碼
這樣子就編寫了一個屬於自己的動態代理,當然,代理方法還不完善,只是針對本示例進行了編寫,有興趣的朋友可以試試將其改為更通用的程式碼。
CGlib 動態代理
下面來看一下 CGlib 的動態代理的使用
使用
先建立RealSubject
類,注意,這個類不用實現任何介面:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:22
*/
public class RealSubject {
public void request(){
System.out.println("真實處理邏輯!");
}
}複製程式碼
然後建立RealSubjectCglibDynamicProxy
代理類,它必須實現MethodInterceptor
介面:
/**
* @author eamon.zhang
* @date 2019-10-09 下午4:23
*/
public class RealSubjectCglibDynamicProxy implements MethodInterceptor {
public Object getInstance(Class<?> clazz) {
// 通過CGLIB動態代理獲取代理物件的過程
Enhancer enhancer = new Enhancer();
// 要把哪個設定為即將生成的新類父類
enhancer.setSuperclass(clazz);
// 設定回撥物件
enhancer.setCallback(this);
// 建立代理物件
return enhancer.create();
}
@Override
public Object intercept(Object obj,Object[] args,MethodProxy proxy) throws Throwable {
before();
Object invokeSuper = proxy.invokeSuper(obj,args);
after();
return invokeSuper;
}
private void before() {
System.out.println("前置增強!");
}
private void after() {
System.out.println("後置增強!");
}
}複製程式碼
這樣,一個簡單的CGlib
動態代理實現就完成了,我們現在來建立測試程式碼:
@Test
public void test(){
RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
instance.request();
}複製程式碼
測試結果:
前置增強!
真實處理邏輯!
後置增強!複製程式碼
原理分析
不管是JDK Proxy
還是CGlib
,他們的核心內容都是去建立代理類,所以我們只要去了解其建立代理類的過程就 OK 了。
從上面簡單的使用示例可以知道,要使用 CGlib 動態代理,代理類必須要實現MethodInterceptor
(方法攔截器),MethodInterceptor
介面原始碼如下:
/**
* General-purpose {@link Enhancer} callback which provides for "around advice".
* @author Juozas Baliuka <a href="mailto:[email protected]">[email protected]</a>
* @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $
*/
public interface MethodInterceptor
extends Callback
{
/**
* All generated proxied methods call this method instead of the original method.
* The original method may either be invoked by normal reflection using the Method object,* or by using the MethodProxy (faster).
* @param obj "this",the enhanced object
* @param method intercepted Method
* @param args argument array; primitive types are wrapped
* @param proxy used to invoke super (non-intercepted method); may be called
* as many times as needed
* @throws Throwable any exception may be thrown; if so,super method will not be invoked
* @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
* @see MethodProxy
*/
public Object intercept(Object obj,java.lang.reflect.Method method,MethodProxy proxy) throws Throwable;
}複製程式碼
介面中只有一個intercept
方法,其中傳入的引數:
-
obj
表示增強的物件,即實現這個介面類的一個物件; -
method
表示要被攔截的方法; -
args
表示方法引數; -
proxy
表示要觸發父類的方法物件;
在建立代理物件的邏輯getInstance(Class> clazz)
中,呼叫了enhancer.create()
方法,我們跟進原始碼看一下:
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}複製程式碼
原始碼註釋內容翻譯:如有必要,生成一個新類,並使用指定的回撥(如果有)來建立一個新的物件例項。 使用的父類的引數的構造方法來例項化父類。
它的核心內容是在createHelper();
方法中:
private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,ReflectUtils.getNames(interfaces),filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),callbackTypes,useFactory,interceptDuringConstruction,serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}複製程式碼
preValidate()
方法的作用是,前置校驗,校驗callbackTypes、filter
是否為空,以及為空時的處理。
然後通過KEY_FACTORY.newInstance()
方法建立EnhancerKey
物件,並將其作為super.create(key)
方法的引數傳入,我們來看一下這個create()
方法,發現它是Enhancer
類的父類AbstractClassGenerator
中的一個方法:
protected Object create(Object key) {
try {
ClassLoader loader = getClassLoader();
Map<ClassLoader,ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader,ClassLoaderData> newCache = new WeakHashMap<ClassLoader,ClassLoaderData>(cache);
data = new ClassLoaderData(loader);
newCache.put(loader,data);
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this,getUseCache());
if (obj instanceof Class) {
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}複製程式碼
這個方法在最後呼叫了 nextInstance(obj)
方法,它對應的實現,是在Enhancer
類中:
protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return data.generatedClass;
}
Class[] argumentTypes = this.argumentTypes;
Object[] arguments = this.arguments;
if (argumentTypes == null) {
argumentTypes = Constants.EMPTY_CLASS_ARRAY;
arguments = null;
}
return data.newInstance(argumentTypes,arguments,callbacks);
}複製程式碼
這裡又呼叫了data.newInstance(argumentTypes,callbacks)
方法,第一個引數為代理物件的構造器型別,第二個為代理物件構造方法引數,第三個為對應回撥物件。原始碼如下:
public Object newInstance(Class[] argumentTypes,Object[] arguments,Callback[] callbacks) {
setThreadCallbacks(callbacks);
try {
// Explicit reference equality is added here just in case Arrays.equals does not have one
if (primaryConstructorArgTypes == argumentTypes ||
Arrays.equals(primaryConstructorArgTypes,argumentTypes)) {
// If we have relevant Constructor instance at hand,just call it
// This skips "get constructors" machinery
return ReflectUtils.newInstance(primaryConstructor,arguments);
}
// Take a slow path if observing unexpected argument types
return ReflectUtils.newInstance(generatedClass,argumentTypes,arguments);
} finally {
// clear thread callbacks to allow them to be gc'd
setThreadCallbacks(null);
}
}複製程式碼
我們發現這裡面的邏輯的意思就是,根據傳進來的引數,通過反射來生成物件,我們可以利用cglib
的代理類可以將記憶體中的 class
檔案寫入本地磁碟:
@Test
public void test1(){
//利用 cglib 的代理類可以將記憶體中的 class 檔案寫入本地磁碟
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/eamon.zhang/Documents/cglib");
RealSubjectCglibDynamicProxy proxy = new RealSubjectCglibDynamicProxy();
RealSubject instance = (RealSubject) proxy.getInstance(RealSubject.class);
instance.request();
}複製程式碼
執行之後,在對應的目錄中可以看到生成了下圖中這三個.class
檔案:
通過除錯跟蹤,我們發現 RealSubject$$EnhancerByCGLIB$$5389cdca
就是 CGLib
生成的代理類,繼承了 RealSubject
類。通過IDEA
檢視該原始碼:
public class RealSubject$$EnhancerByCGLIB$$5389cdca extends RealSubject implements Factory {
...
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject$$EnhancerByCGLIB$$5389cdca");
Class var1;
CGLIB$request$0$Method = ReflectUtils.findMethods(new String[]{"request","()V"},(var1 = Class.forName("com.eamon.javadesignpatterns.proxy.dynamic.cglib.RealSubject")).getDeclaredMethods())[0];
CGLIB$request$0$Proxy = MethodProxy.create(var1,var0,"()V","request","CGLIB$request$0");
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals","(Ljava/lang/Object;)Z","toString","()Ljava/lang/String;","hashCode","()I","clone","()Ljava/lang/Object;"},(var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1,"equals","CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1,"CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1,"CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1,"()Ljava/lang/Object;","CGLIB$clone$4");
}
final void CGLIB$request$0() {
super.request();
}
public final void request() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this,CGLIB$request$0$Method,CGLIB$emptyArgs,CGLIB$request$0$Proxy);
} else {
super.request();
}
}
...
}複製程式碼
我們通過代理類的原始碼可以看到,代理類會獲得所有在父類繼承來的方法,並且會有 MethodProxy
與之對應,比如 Method CGLIB$request$0$Method
、MethodProxy CGLIB$request$0$Proxy
這些方法在代理類的 reuqest()
中都有呼叫。
呼叫過程: 代理物件呼叫 this.request()
方法 -> 呼叫攔截器 -> methodProxy.invokeSuper
-> CGLIB$request$0()
-> 被代理物件 request()
方法。 此時,我們發現攔截器 MethodInterceptor
中就是由 MethodProxy
的 invokeSuper
方法呼叫代理方法的。
MethodProxy 非常關鍵,我們分析一下它具體做了什麼:
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private CreateInfo createInfo;
private final Object initLock = new Object();
private volatile FastClassInfo fastClassInfo;
/**
* For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class
* for similar functionality.
*/
public static MethodProxy create(Class c1,Class c2,String desc,String name1,String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1,desc);
proxy.sig2 = new Signature(name2,desc);
proxy.createInfo = new CreateInfo(c1,c2);
return proxy;
}
...
private static class CreateInfo
{
Class c1;
Class c2;
NamingPolicy namingPolicy;
GeneratorStrategy strategy;
boolean attemptLoad;
public CreateInfo(Class c1,Class c2)
{
this.c1 = c1;
this.c2 = c2;
AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
if (fromEnhancer != null) {
namingPolicy = fromEnhancer.getNamingPolicy();
strategy = fromEnhancer.getStrategy();
attemptLoad = fromEnhancer.getAttemptLoad();
}
}
}
...複製程式碼
繼續看invokeSuper()
方法:
public Object invokeSuper(Object obj,Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2,obj,args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private static class FastClassInfo
{
FastClass f1;
FastClass f2;
int i1;
int i2;
}複製程式碼
上面程式碼呼叫過程就是獲取到代理類對應的 FastClass
,並執行了代理方法。還記得之前生成三個 class
檔案嗎?RealSubject$$EnhancerByCGLIB$$5389cdca$$FastClassByCGLIB$$57b94d72.class
就是代理類的 FastClass
,RealSubject$$FastClassByCGLIB$$ed23432.class
就是被代理類的FastClass
。
CGLib
動態代理執行代理方法效率之所以比 JDK
的高是因為 Cglib
採用了 FastClass
機 制,它的原理簡單來說就是:
- 為代理類和被代理類各生成一個 Class,這個 Class 會為代理類或被代理類的方法分配一個
index(int 型別)
。這個index
當做一個入參,FastClass
就可以直接定位要呼叫的方法直接進行呼叫,這樣省去了反射呼叫,所以呼叫效率比JDK
動態代理通過反射呼叫高。
至此,Cglib 動態代理的原理我們就基本搞清楚了,如果對程式碼細節有興趣的小夥伴可以再自行深入研究。
JDK Proxy 與 CGlib 比較
-
JDK
動態代理是實現了被代理物件的介面,CGLib
是繼承了被代理物件。 -
JDK
和CGLib
都是在執行期生成位元組碼,JDK
是直接寫Class
位元組碼,CGLib
使用ASM
框架寫Class
位元組碼,Cglib
代理實現更復雜,生成代理類 比JDK
效率低。 -
JDK
呼叫代理方法,是通過反射機制呼叫,CGLib
是通過FastClass
機制直接呼叫方法,CGLib
執行效率 更高
代理模式與 Spring
Spring 中的代理選擇原則
- 當
Bean
有實現介面時,Spring
就會用JDK
的動態代理 - 當
Bean
沒有實現介面時,Spring
選擇CGLib
。 -
Spring
可以通過配置強制使用CGLib
,只需在Spring
的配置檔案中加入如下程式碼:
<aop:aspectj-autoproxy proxy-target-class="true"/>複製程式碼
參考資料:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html
總結
靜態代理和動態的本質區別
- 靜態代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增,違背開閉原則。
- 動態代理採用在執行時動態生成程式碼的方式,取消了對被代理類的擴充套件限制,遵循開閉原則。
- 若動態代理要對目標類的增強邏輯擴充套件,結合策略模式,只需要新增策略類便可完成,無需修改代理類的程式碼。
代理模式的優缺點
優點
- 代理模式能將代理物件與真實被呼叫的目標物件分離。
- 一定程度上降低了系統的耦合度,擴充套件性好。
- 可以起到保護目標物件的作用。
- 可以對目標物件的功能增強
缺點
- 代理模式會造成系統設計中類的數量增加。
- 在客戶端和目標物件增加一個代理物件,會造成請求處理速度變慢。
- 增加了系統的複雜度。
---
本篇文章的原始碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/main/java/com/eamon/javadesignpatterns/proxy
測試類原始碼目錄:https://github.com/eamonzzz/java-advanced/tree/master/01.DesignPatterns/design-patterns/src/test/java/com/eamon/javadesignpatterns/proxy
歡迎大家 star 原始碼,共同進步,我會按照 git 上的大綱在學習的同時,記錄文章與原始碼~
博主剛開始寫部落格不久,文中若有錯誤或者有任何的建議,請在留言中指出,向大家學習~
本文由部落格一文多發平臺 OpenWrite 釋出!