寫在前面
未經允許不得轉載!
- 什麼是代理?
- (我的理解)在編程語言中,代理就是由於某些原因控制某個對象/方法的訪問
- 爲什麼要有靜態代理?
- 開閉原則,需求總是變化無常的,在不修改原有邏輯的情況下,對其進行拓展
- 爲什麼要有動態代理?
- 用靜態代理的原因,動態代理也有
- 方便維護,從靜態代理的維護實現類到現在只需要維護拓展邏輯
這篇文章有好多隱藏的知識點,用心看用心挖掘,最好自己寫一遍代碼,走一遍完整的邏輯。
靜態代理
靜態代理感覺沒啥好說的,實現方式可以是組合,也可以是繼承,當然是推薦用組合的方式實現靜態代理,理由是解耦,可以任意組裝需要的組合,比如說你要對原邏輯代理3次(我的案例可以拆成兩個代理,一個校驗參數,一個打印信息),進行不同的業務邏輯,且執行的順序可能會變化。
代碼看附錄,需要用到的類:
Calculable
,Calculator
,CalculatorStaticProxy
,CalculatorStaticProxyTest
。
動態代理
動態代理相對於靜態代理來說,目標是一致的,就是控制目標方法的訪問,但是代理類是動態生成的。
重點來了:動態生成代理類
,生成方式也有好多種,比如說:生成新的一個代理類,將原類改造成代理類。
JDK 動態代理
(這裏只說果,瞭解因看下面的原理解析),還有排除
java.lang.Object
(本來就是所有實現類的 super class)
這段很重要,結論:
代理類一定是java.lang.reflect.Proxy
的子類(所以 JDK 動態代理只能代理有interface
的類及其方法),並且implements
了指定類的所有interface
,並且一定有且只有一個帶java.lang.reflect.InvocationHandler
參數的Constructor
方法,實例化對象的時候一定會將 InvocationHandler 帶入。
還有個很重要的點: 爲什麼創建代理對象的時候,要指定 ClassLoder?
我覺得有兩個原因:
* 可以實現安全機制,註釋上有說明
* 跟隨目標類進行同步熱加載類
然後,當你調用某個
interface
的方法時,最終會轉發給上面構造器帶入的那個對象的invoke
方法,搞定,收工。
代碼看附錄,需要用到的類:
Calculable
,Calculator
,CalculatorJdkDynamicProxyHandler
,CalculatorJdkDynamicProxyTest
,$CalculatorJdkDynamicProxy
(這個類就是生成的代理類)。
基於JDK 8u201
原理解析
想要動態生成對象的前提是:要有這個對象的 Class 對象。
想要有這個對象的 Class 對象的前提是:要有這個對象的 class (也就是字節碼),然後通過 ClassLoader 加載到方法區的。
所以問題就在怎麼動態生成字節碼,並且怎麼加載到方法區的。
那麼來看看JDK 動態代理怎麼實現的
解析基於
JDK 8u201
先來看看代理類的結構:
- 第1部分:定義 class 的那段
- 第2部分:定義 fields 的那段
- 第3部分:定義 constructor methods 的那段
- 第4部分:定義 methods 的那段
然後來看看代理類的字節碼是怎麼生成的(只說關鍵部分):
- 調用順序:
java.lang.reflect.Proxy#newProxyInstance
java.lang.reflect.Proxy#getProxyClass0
proxyClassCache.get
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
java.lang.reflect.WeakCache#get
(不重要)subKeyFactory.apply(key, parameter)
(不重要)java.lang.reflect.Proxy.KeyFactory#apply
factory = new Factory(key, parameter, subKey, valuesMap);
supplier = factory;
supplier.get()
java.lang.reflect.WeakCache.Factory#get
valueFactory.apply
java.lang.reflect.Proxy.ProxyClassFactory#apply
走到這裏開始最重要的那塊了 !!!- class name 怎麼生成的,怎麼拿到全部的
interface
自己看- 先來看下怎麼加載動態生成的字節碼的
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
- 不是通過雙親委派的形式加載的,但是這個動態生成的類加載器是
AppClassLoader
- 再來看怎麼生成字節碼的
sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
- 如果要保存生成的代理類,查看
saveGeneratedFiles
這個字段- 再來看生成字節碼的邏輯
sun.misc.ProxyGenerator#generateClassFile
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
- 進入
sun.misc.ProxyGenerator#generateClassFile
方法後,第一次看建議反着看,從結果往前推進- 從結果那可以看出,這字節碼是 jdk 自己生成的
- 然後跟着上面的代理類的結構一步步看上去
- 解密上面的一些結論:
- 有且只有一個帶
java.lang.reflect.InvocationHandler
參數的Constructor
方法
this.methods.add(this.generateConstructor());
- 爲什麼父類是Proxy
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
- 下面附上了 ClassFile Structure,助你一臂之力,是不是豁然開朗了 ^^
- 從
var14.writeInt(-889275714);
對應的是 magic: cafebabe- 一步步對過來
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
知識補充
- 除了 ClassLoder ,還可以用
sun.misc.Unsafe#defineClass
來把加載類信息
附錄
public interface Calculable {
Integer divide(int a, int b);
}
public class Calculator implements Calculable {
@Override
public Integer divide(int a, int b) {
return a / b;
}
}
public class CalculatorStaticProxy implements Calculable {
private Calculable target;
public CalculatorStaticProxy(Calculable target) {
this.target = target;
}
@Override
public Integer divide(int a, int b) {
Integer result = null;
boolean isPass = beforeDivideProxy(a, b);
if (isPass) {
result = target.divide(a, b);
afterDivideProxy(a, b);
}
return result;
}
private boolean beforeDivideProxy(int a, int b) {
System.out.printf("before exec %d / %d\n", a, b);
if (b == 0) {
return false;
}
return true;
}
private void afterDivideProxy(int a, int b) {
System.out.printf("after divide method\n");
}
}
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class CalculatorStaticProxyTest {
@Test
public void testDivide_normal() {
Calculable calc = new CalculatorStaticProxy(new Calculator());
assertEquals(Integer.valueOf(2), calc.divide(6, 3));
}
@Test
public void testDivide_exception() {
Calculable calc = new CalculatorStaticProxy(new Calculator());
assertNull(calc.divide(6, 0));
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CalculatorJdkDynamicProxyHandler implements InvocationHandler {
private Calculable target;
public CalculatorJdkDynamicProxyHandler(Calculable target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("divide".equals(methodName)) {
return handleDivideMethod(method, args);
}
return method.invoke(target, args);
}
private Integer handleDivideMethod(Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException
{
Integer result = null;
boolean isPass = beforeDivideProxy(method, args);
if (isPass) {
result = (Integer) method.invoke(target, args);
afterDivideProxy(method, args);
}
return result;
}
private boolean beforeDivideProxy(Method method, Object[] args) {
System.out.printf("before exec divide method\n", args[0], args[1]);
if ((Integer) args[1] == 0) {
return false;
}
return true;
}
private void afterDivideProxy(Method method, Object[] args) {
System.out.printf("after divide method\n");
}
}
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class CalculatorJdkDynamicProxyTest {
private Calculable proxyCalc = null;
// public static void main(String[] args) {
// // 保存生成的代理類的字節碼文件: jdk8 下的配置,默認生成的路徑是項目根目錄
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//
// sun.misc.ProxyGenerator.generateProxyClass(
// "$CalculatorJdkDynamicProxy",
// Calculator.class.getInterfaces(),
// java.lang.reflect.Modifier.PUBLIC);
// }
@Before
public void beforeTest() {
Calculable calc = new Calculator();
ClassLoader loader = calc.getClass().getClassLoader();
Class<?>[] interfaces = calc.getClass().getInterfaces();
InvocationHandler h = new CalculatorJdkDynamicProxyHandler(calc);
proxyCalc = (Calculable) Proxy.newProxyInstance(loader, interfaces, h);
System.out.println(proxyCalc.getClass().getName());
System.out.println(proxyCalc.getClass().getClassLoader());
}
@Test
public void testDivide_normal() {
assertEquals(Integer.valueOf(2), proxyCalc.divide(6, 3));
}
@Test
public void testDivide_exception() {
Calculable calc = new CalculatorStaticProxy(new Calculator());
assertNull(calc.divide(6, 0));
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public class $CalculatorJdkDynamicProxy extends Proxy implements Calculable {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $CalculatorJdkDynamicProxy(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 Integer divide(int var1, int var2) throws {
try {
return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
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("Calculable").getMethod("divide", Integer.TYPE, Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}