1.前言 首先回顧下代理模式(Proxy Pattern)的定義:代理模式指為其他對象提供一種代理,以控制這個對象的訪問,屬於結構型設計模式。其適用於在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端於目標對象之間起到中介的作用。 代理模式主要分為靜態代理和動態代理兩種方 ...
1.前言
首先回顧下代理模式(Proxy Pattern)的定義:代理模式指為其他對象提供一種代理,以控制這個對象的訪問,屬於結構型設計模式。其適用於在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端於目標對象之間起到中介的作用。
代理模式主要分為靜態代理和動態代理兩種方式,靜態代理需要手動創建代理類,代理的目標對象是固定的;動態代理使用反射機制,代理的目標對象是活動的,不需要創建代理類即可給不同的目標隨時創建代理。本篇重點探究動態代理的實現。
2.JDK動態代理
JDK動態代理採用位元組重組,重新生成對象來替代原始對象,以達到動態代理的目的。JDK動態代理生成對象的步驟如下:
- 獲取被代理對象的引用,並且獲取它的所有介面,反射獲取。
- JDK動態代理類重新生成一個新的類,同時新的類要實現被代理類實現的所有介面。
- 動態生成Java代碼,新加的業務邏輯方法由一定的邏輯代碼調用(在代碼中體現)。
- 編譯新生成的Java代碼
.class
文件。 - 重新載入到JVM中運行。
2.1 JDK動態代理實現及原理源碼解析
實現一個JDK動態代理,方式為實現java.lang.reflect.InvocationHandler
介面,並使用java.lang.reflect.Proxy.newProxyInstance()
方法生成代理對象。
/**
* 要代理的介面
*/
public interface IPerson {
void learn();
}
/**
* 真實調用類
*/
public class Zhangsan implements IPerson {
public void learn() {
System.out.println("==張三學習中間件==");
}
}
/**
* JDK代理類生成
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkInvocationHandler implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target){
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target,args);
after();
return result;
}
private void before() {
System.out.println("事前做好計劃");
}
private void after() {
System.out.println("事後回顧梳理");
}
}
/**
* 測試
*/
public class TestProxy {
public static void main(String[] args) {
try {
//把生成的位元組碼保存到本地磁碟,動態生成的類會保存在工程根目錄下的 com/sun/proxy 目錄裡面
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IPerson obj = (IPerson) new JdkInvocationHandler().getInstance(new Zhangsan());
obj.learn();
} catch (Exception e) {
e.printStackTrace();
}
}
}
看下 Proxy.newProxyInstance
裡面究竟發生了什麼?
結合流程圖,在生成位元組碼的那個地方,也就是 ProxyGenerator.generateProxyClass()
方法裡面,通過代碼可以看到(自行查閱,篇幅原因,這裡不貼代碼),裡面是用參數 saveGeneratedFiles
來控制是否把生成的位元組碼保存到本地磁碟。代碼中已經設置保存到本地,現在找到剛纔生成的 $Proxy0.class
,反編譯打開如下:
import com.zang.jdkproxy.IPerson;
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 IPerson {
private static Method m1;
private static Method m3;
private static Method m2;
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 void learn() throws {
try {
// super.h 對應的是父類的h變數,也就是Proxy.newProxyInstance方法中的InvocationHandler參數
// 所以這裡實際上就是使用了我們自己寫的InvocationHandler實現類的invoke方法
super.h.invoke(this, m3, (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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.zang.jdkproxy.IPerson").getMethod("learn");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到 $Proxy0
類繼承了Proxy
類,裡面有一個跟IPerson
一樣簽名的 learn
方法,方法實現中的super.h.invoke(this, m3, (Object[])null);
,super.h 對應的是父類的h變數,也就是Proxy.newProxyInstance
方法中的InvocationHandler
參數:
package java.lang.reflect;
//import略
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
//
所以這裡實際上就是使用了我自己寫的InvocationHandler
實現類JdkInvocationHandler
的invoke
方法,當調用 IPerson.learn
的時候,其實它是被轉發到了 JdkInvocationHandler.invoke
。至此,整個魔術過程就透明瞭。
2.2 手寫JDK動態代理
使用JDK動態代理的類名和方法名定義以及執行思路,下麵來進行手寫實現。
創建MyInvocationHandler
介面:
import java.lang.reflect.Method;
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
創建MyProxy類:
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 自己實現的代理類,用來生成位元組碼文件,並動態載入到JVM中
*/
public class MyProxy {
public static final String ln = "\r\n";
/**
* 生成代理對象
* @param classLoader 類載入器,用於載入被代理類的類文件
* @param interfaces 被代理類的介面
* @param h 自定義的InvocationHandler介面,用於具體代理方法的執行
* @return 返回被代理後的代理對象
*/
public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h) {
try {
//1、動態生成源代碼.java文件
String src = generateSrc(interfaces);
//2、Java文件輸出磁碟
String filePath = MyProxy.class.getResource("").getPath();
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//3、把生成的.java文件編譯成.class文件
//獲取Java編譯器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//標註Java文件管理器,用來獲取Java位元組碼文件
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(f);
//創建task,通過java位元組碼文件將類信息載入到JVM中
JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
//開始執行task
task.call();
//關閉管理器
manage.close();
//4、編譯生成的.class文件載入到JVM中來
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
f.delete();
//5、返回位元組碼重組以後的新的代理對象
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成代理類的源代碼
*/
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append(MyProxy.class.getPackage() + ";" + ln);
sb.append("import " + interfaces[0].getName() + ";" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("GPInvocationHandler h;" + ln);
sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()) {
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if (i > 0 && i < params.length - 1) {
paramNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
sb.append("}catch(Error _ex) { }");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);" + ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
private static Map<Class, Class> mappings = new HashMap<Class, Class>();
static {
mappings.put(int.class, Integer.class);
}
private static String getReturnEmptyCode(Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "return 0;";
} else if (returnClass == void.class) {
return "";
} else {
return "return null;";
}
}
private static String getCaseCode(String code, Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
private static String toLowerFirstCase(String src) {
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
創建類載入器MyClassLoader:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader {
private File classPathFile;
public MyClassLoader(){
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
/**
* 通過類名稱載入類位元組碼文件到JVM中
* @param name 類名
* @return 類的Class獨享
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//獲取類名
String className = MyClassLoader.class.getPackage().getName() + "." + name;
if(classPathFile != null){
//獲取類文件
File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
if(classFile.exists()){
//將類文件轉化為位元組數組
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte [] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1){
out.write(buff,0,len);
}
//調用父類方法生成class實例
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
}
實現並測試
/**
* 要代理的介面
*/
public interface IPerson {
void learn();
}
/**
* 真實調用類
*/
public class Zhangsan implements IPerson {
public void learn() {
System.out.println("==張三學習中間件==");
}
}
/**
* JDK代理類生成
*/
public class CustomInvocationHandler implements MyInvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target){
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target,args);
after();
return result;
}
private void before() {
System.out.println("事前做好計劃");
}
private void after() {
System.out.println("事後回顧梳理");
}
}
/**
* 測試
*/
public class Test {
public static void main(String[] args) {
CustomInvocationHandler custom = new CustomInvocationHandler();
IPerson zhangsan = custom.getInstance(new Zhangsan());
zhangsan.learn();
}
}
至此,手寫完成,讀者也可自行參照實現。
3.CGLib動態代理API原理分析
3.1 CGLib動態代理的使用
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CustomCGlib implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception{
//相當於Proxy,代理的工具類
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
private void before() {
System.out.println("事前做好計劃");
}
private void after() {
System.out.println("事後回顧梳理");
}
}
這裡有一個小細節,CGLib動態代理的目標對象不需要實現任何介面,它是通過動態繼承目標對象實現動態代理的,客戶端測試代碼如下:
public class CglibTest {
public static void main(String[] args) {
try {
Zhangsan obj = (Zhangsan) new CustomCGlib().getInstance(Zhangsan.class);
obj.learn();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2 CGLib動態代理的實現原理
CGLib動態代理的實現原理又是怎樣的呢?可以在客戶端測試代碼中加上一句代碼,將CGLib動態代理後的.class
文件寫入磁碟,然後反編譯來一探究竟,代碼如下:
//import net.sf.cglib.core.DebuggingClassWriter;
//使用CGLib的代理類可以將記憶體中的.class文件寫入本地磁碟
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
Zhangsan obj = ···
//···
重新執行代碼,再輸出目錄下會出現三個.class文件,一個是目標(被代理)類的FastClass,一個是代理類,一個是代理類的FastClass。如圖:
其中,Zhangsan$$EnhancerByCGLIB$$3d23e0ea.class
就是CGLib動態代理生成的代理類,繼承了Zhangsan
類。
package com.zang.cglibproxy;
import java.lang.reflect.Method;
import net.sf.cglib.*;
public class Zhangsan$$EnhancerByCGLIB$$3d23e0ea extends Zhangsan implements Factory {
//···
//傳入的MethodInterceptor對象
private MethodInterceptor CGLIB$CALLBACK_0;
//目標類的learn方法對象
private static final Method CGLIB$learn$0$Method;
//代理類的learn方法對象
private static final MethodProxy CGLIB$learn$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
//初始化方法,其中部分代碼略
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.zang.cglibproxy.Zhangsan$$EnhancerByCGLIB$$78b38660");
Class var1;
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());
//···
//初始化目標類的learn方法對象
CGLIB$learn$0$Method = ReflectUtils.findMethods(new String[]{"learn", "()V"}, (var1 = Class.forName("com.zang.cglibproxy.Zhangsan")).getDeclaredMethods())[0];
//初始化代理類的learn方法對象
CGLIB$learn$0$Proxy = MethodProxy.create(var1, var0, "()V", "learn", "CGLIB$learn$0");
}
//這裡直接調用Zhangsan#learn
final void CGLIB$learn$0() {
super.learn();
}
public final void learn() {
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$learn$0$Method, CGLIB$emptyArgs, CGLIB$learn$0$Proxy);
} else {
super.learn();
}
}
//···
}
調用過程為:代理對象調用this.learn
方法→調用攔截器→methodProxy.invokeSuper()
→CGLIB$learn$0
→被代理對象learn
方法。
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
public class CustomCGlib implements MethodInterceptor {
//···
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
//···
}
MethodInterceptor
攔截器就是由MethodProxy
的invokeSuper
方法調用代理方法的,因此,MethodProxy
類中的代碼非常關鍵,下麵分析它具體做了什麼:
package net.sf.cglib.proxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.*;
public class MethodProxy {
private Signature sig1;
private Signature sig2;
private MethodProxy.CreateInfo createInfo;
private final Object initLock = new Object();
private volatile MethodProxy.FastClassInfo fastClassInfo;
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
//創建目標類的FastClass對象(在緩存中,則取出;沒在,則重新生成)
fci.f1 = helper(ci, ci.c1);
//創建代理類的FastClass對象
fci.f2 = helper(ci, ci.c2);
//獲取learn方法的索引
fci.i1 = fci.f1.getIndex(this.sig1);
//獲取CGLIB$learn$0方法的索引
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
}
}
}
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
//初始化,創建了兩個FastClass類對象
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
//這裡將直接調用代理類的CGLIB$learn$0方法,而不是通過反射調用
//fci.f2:代理類的FastClass對象,fci.i2為CGLIB$learn$0方法對應的索引,obj為當前的代理類對象,args為learn方法的參數列表
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
上面代碼調用獲取代理類對應的FastClass,並執行代理方法。還記得之前生成的三個.class
文件嗎?Zhangsan$$EnhancerByCGLIB$$78b38660$$FastClassByCGLIB$$a8f9873c.class
就是代理類的FastClass,Zhangsan$$FastClassByCGLIB$$bcf7b1f4.class
就是目標類的FastClass。
CGLib動態代理執行代理方法的效率之所以比JDK高,是因為CGlib採用了FastClass機制,它的原理簡單來說就是:為代理類和被代理類各生成一個類,這個類會為代理類或被代理類的方法分配一個index(int類型);這個index被當作一個入參,FastClass可以直接定位要調用的方法並直接進行調用,省去了反射調用,因此調用效率比JDK動態代理通過反射調用高(並不絕對,還需參考JDK版本及使用場景來說)。下麵來反編譯一個FastClass。
public class Zhangsan$$FastClassByCGLIB$$bcf7b1f4 extends FastClass {
public Zhangsan$$FastClassByCGLIB$$bcf7b1f4(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case 1574139569:
if (var10000.equals("learn()V")) {
//learn方法返回0
return 0;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
//···
}
}
}
//根據index獲取方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
Zhangsan var10000 = (Zhangsan)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
//傳入index為0則執行learn方法
var10000.learn();
return null;
case 1:
return new Boolean(var10000.equals(var3[0]));
//···
FastClass並不是跟代理類一起生成的,而是在第一次執行MethodProxy
的invoke
或invokeSuper
方法時生成的,並被放在了緩存中。
4.總結
通過上面的分析,相信會對兩種動態代理的實現原理有一個深入的認識,總結性比較兩者的區別如下:
- JDK動態代理實現了被代理對象的介面,CGLib動態代理繼承了被代理對象。
- JDK動態代理和CGLib動態代理都在運行期生成位元組碼,JDK動態代理直接寫Class位元組碼,CGLib動態代理使用ASM框架寫Class位元組碼。CGLib動態代理實現更複雜,生成代理類比JDK動態代理效率低。
- JDK動態代理調用代理方法是通過反射機制調用的,CGLib動態代理是通過FastClass機制直接調用方法的,CGLib動態代理的執行效率更高。