27--靜態代理模式和JDK、CGLIB動態代理
前面的章節,已經分析了IoC容器的原始碼,接下來的章節來分析Spring的另一個核心功能AOP。為了更好的分析原始碼,需要先溫習一下動態代理的知識,如果對java的動態代理無所瞭解的話,那麼對AOP原始碼的分析就無從談起。代理模式可分為靜態代理和動態代理兩種。而動態代理又有JDK、CGLIB動態代理。下面我們逐步分析這幾種代理。
1.靜態代理
- 被代理介面和實現類
package com.lyc.cn.v2.day04.proxy.proxy;
/**
* 賬戶介面
*/
public interface Count {
// 查詢賬戶
void queryCount();
// 修改賬戶
void updateCount();
}
package com.lyc.cn.v2.day04.proxy.proxy;
/**
* @author: LiYanChao
* @create: 2018-10-21 12:07
*/
public class CountImpl implements Count {
@Override
public void queryCount() {
System.out.println("==查詢賬戶");
}
@Override
public void updateCount() {
System.out.println("==更新賬戶" );
}
}
介面只模擬了兩個介面,賬戶查詢和賬戶更新,並在實現類裡進行了列印。
- 代理類
package com.lyc.cn.v2.day04.proxy.proxy;
/**
* 代理類
* @author: LiYanChao
* @create: 2018-10-21 12:08
*/
public class CountProxy implements Count {
private CountImpl countImpl;
/**
* 覆蓋預設構造器
*/
public CountProxy(CountImpl countImpl) {
this.countImpl = countImpl;
}
@Override
public void queryCount() {
System.out.println("==查詢賬戶開始");
// 呼叫真正的查詢賬戶方法
countImpl.queryCount();
System.out.println("==查詢賬戶結束");
}
@Override
public void updateCount() {
System.out.println("==更新賬戶開始");
// 呼叫真正的修改賬戶操作
countImpl.updateCount();
System.out.println("==更新賬戶結束");
}
}
- 測試及結果
@Test
public void test1() {
// 靜態代理
CountImpl countImpl = new CountImpl();
CountProxy countProxy = new CountProxy(countImpl);
countProxy.updateCount();
System.out.println("\n*******\n");
countProxy.queryCount();
}
==更新賬戶開始
==更新賬戶
==更新賬戶結束
*******
==查詢賬戶開始
==查詢賬戶
==查詢賬戶結束
該中模式比較簡單,不再做過多的分析。
2.JDK動態代理
JDK動態代理所用到的代理類在程式呼叫到代理類物件時才由JVM真正建立,JVM根據傳進來的業務實現類物件以及方法名 ,動態地建立了一個代理類的class檔案並被位元組碼引擎執行,然後通過該代理類物件進行方法呼叫。我們需要做的,只需指定代理類的預處理、呼叫後操作即可。JDK的動態代理需要實現InvocationHandler介面,並重寫invoke方法。並且被代理的類必須有介面,來看具體的例子:
- 被代理介面和類
package com.lyc.cn.v2.day04.proxy.jdk;
public interface JDKAnimal {
void sayHello();
}
package com.lyc.cn.v2.day04.proxy.jdk;
public class JDKDog implements JDKAnimal {
@Override
public void sayHello() {
System.out.println("我是一隻貓");
}
}
- 自定義InvocationHandler
package com.lyc.cn.v2.day04.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler {
// 目標物件
private Object target;
/**
* 構造方法
* @param target 目標物件
*/
public MyInvocationHandler(Object target) {
super();
this.target = target;
}
/**
* @param proxy JDK動態生成的最終代理物件
* @param method 呼叫真實物件的某個方法的Method物件
* @param args 呼叫真實物件某個方法時接受的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("==代理方法開始執行");
Object invoke = method.invoke(target, args);
System.out.println("==代理方法結束執行");
return invoke;
}
/**
* 獲取目標物件的代理物件
* @return 代理物件
*/
public Object getProxy() {
/**
* 引數:
* 1、獲取當前執行緒的類載入
* 2、獲取介面
* 3、當前物件
*/
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(),
this);
}
}
- 測試及結果
@Test
public void test2() {
// JDK動態代理
MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
JDKAnimal proxy = (JDKAnimal) handler.getProxy();
proxy.sayHello();
}
==代理方法開始執行
我是一隻貓
==代理方法結束執行
3.JDK動態代理原理分析
在MyInvocationHandler類中,通過實現InvocationHandler並重寫invoke方法,實現了對JDKDog類的動態代理。但是這裡大家一定會有一個疑問,在測試類中通過JDKAnimal proxy = (JDKAnimal) handler.getProxy();
獲取了代理類的例項,但是當呼叫proxy.sayHello();
方法時,卻會呼叫MyInvocationHandler的invoke方法,要解開這個謎題,就需要了解一下代理類是如何生成、生成的代理類是什麼樣子的、是如何被例項化的。
- Proxy.newProxyInstance方法簡析 開啟MyInvocationHandler類的getProxy方法,檢視Proxy.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);
}
}
如果大家對這段程式碼不感興趣或者覺得很枯燥的話,沒關係,大家只要知道在這個方法裡生成了代理類的例項就行了,但是生成的代理類是什麼樣子的呢?可以通過ProxyGenerator來幫助我們。
- 生成代理類的.class檔案
package com.lyc.cn.v2.day04.proxy.jdk;
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author: LiYanChao
* @create: 2018-11-02 14:11
*/
public class GenJdkProxyClass {
/**
* 生成代理類的名稱
*/
private static String DEFAULT_CLASS_NAME = "$Proxy";
/**
* 預設生成的檔案全路徑
*/
private static String DEFAULT_FILE_PATH = "/Users/liyanchao/Desktop/" + DEFAULT_CLASS_NAME + ".class";
/**
* 使用ProxyGenerator生成代理類.class檔案
* @param path 檔案路徑
*/
public static void genProxyClass(String path) {
byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class});
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void TestGenProxyClass() {
GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH);
}
}
可以根據自己的需要修改生成的類名和路徑。執行測試方法生成.class文並進行反編譯,如果沒有反編譯軟體的話,可以下載JD-GUI 可以實現對.class檔案的反編譯,該軟體支援windows、linux、macOS。用JD-GUI開啟生成的$Proxy.class檔案,並將反編譯的類匯入IDEA(直接在IDEA裡新建同名檔案將程式碼複製貼上進去即可)。反編譯後的原始碼如下:
package com.lyc.cn.v2.day04.proxy.jdk;
/**
* 反編譯代理.class檔案
* @author: LiYanChao
* @create: 2018-11-02 15:01
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy extends Proxy implements JDKAnimal {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
try {
return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue();
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
/**
* 代理類中實現了sayHello介面
*/
public final void sayHello() {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String) this.h.invoke(this, m2, null);
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer) this.h.invoke(this, m0, null)).intValue();
} catch (Error | RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.lyc.cn.v2.day04.proxy.jdk.JDKAnimal").getMethod("sayHello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
// return 會報錯,註釋掉即可。
//return;
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
代理類$Proxy被宣告為final類,繼承Proxy類並實現了JDKAnimal介面,重寫了equals、toString、hashCode等方法,當然最重要的還是實現了JDKAnimal介面中的sayHello方法,並通過靜態程式碼塊拿到了sayHello方法的資訊,看到sayHello方法體,看到這裡相信大家對前面提到的疑問已經恍然大悟了。再通過一個測試方法加深大家的印象:
@Test
public void test3() {
// JDK動態代理測試反編譯後的生成代理類
MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
$Proxy $proxy = new $Proxy(handler);
$proxy.sayHello();
}
new $Proxy(handler)
建構函式接受的就是我們自定義的MyInvocationHandler,所以當代碼執行到$Proxy的sayHello方法時,this.h.invoke(this, m3, null);
this.h就是MyInvocationHandler的例項,所以自然就會呼叫到invoke方法了,因為JDKAnimal proxy = (JDKAnimal) handler.getProxy();
獲取到的是代理類的例項,而不是JDKAnimal的例項。
4.CGLIB動態代理
CGLIB是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。 在使用的時候需要引入cglib和asm的jar包,並實現MethodInterceptor介面。
本例是在Spring原始碼下新建的Gradle模組,所以引入jar包的方式如下,如果是maven工程或者其他的工程的話,可自行匯入jar包。
compile group: 'asm', name: 'asm', version: '3.3.1'
compile group: 'cglib', name: 'cglib', version: '2.2.2'
- 被代理類
package com.lyc.cn.v2.day04.proxy.cglib;
public class CglibCat {
public void sayHello() {
System.out.println("我是一隻貓。。。");
}
}
- 自定義MethodInterceptor
package com.lyc.cn.v2.day04.proxy.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 MyCglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 這裡的目標型別為Object,則可以接受任意一種引數作為被代理類,實現了動態代理
public Object getInstance(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 返回代理物件
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("==代理方法開始執行")