如題,手寫一個面向接口的動態代理。我們需要先了解jdk中的動態代理是怎么實現的。
理解生成的代碼和調用過程
設置vm參數,-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
,可以使jdk動態生成的class文件輸出到磁盤中。
使用下面代碼進行調試
public interface IService {
public void service(String name )throws Exception;
}
public class ServiceImplA implements IService {
@Override
public void service(String name) throws Exception {
System.out.println("ServiceImplA name" + name);
}
}
public class DynaProxyServiceA implements InvocationHandler {
private Object object;
/**
* 將目標對象關聯到InvocationHandler接口,返回代理對象obj
* 調用代理對象的方法時,都會自動調用invoke方法
*/
public Object bind(Object object){
this.object = object;
return Proxy.newProxyInstance(
this.object.getClass().getClassLoader(),
this.object.getClass().getInterfaces(),
this);
}
@SuppressWarnings("unchecked")
public <T> T bindInterface(Class<T> proxyInterface){
object = proxyInterface;
return (T)Proxy.newProxyInstance(
proxyInterface.getClassLoader(),
new Class[]{proxyInterface},
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("log start");
if(object instanceof Class<?>){
Class<?> clazz = (Class<?>) object;
for (Method clazzMethod : clazz.getDeclaredMethods()) {
System.out.println(clazzMethod.getName());
}
}
try{
result = method.invoke(this.object,args);
Class<?> returnType = method.getReturnType();
System.out.println(returnType);
}catch (Exception e){
throw e;
}
System.out.println("log end");
return result;
}
public static void main(String [] args) throws Exception {
IService service = (IService)new DynaProxyServiceA()
.bind(new ServiceImplA());
service.service("zhjl");
}
}
輸出結果
log start
ServiceImplA namezhjl
void
log end
運行完程序后,會在項目的根目錄生成一個文件夾com.sun.proxy
,里面會生成一個$Proxy0.class
的代理類文件。
打開文件可以看到以下生成的源代碼。
public final class $Proxy0 extends Proxy implements IService {
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 service(String var1) throws Exception {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (Exception | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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("org.example.aop.IService").getMethod("service", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
很容易可以發現,生成的源代碼是有規律可尋的。
- 固定會重寫
java.lang.Object
中的equals
,toString
,hashCode
三個方法 - 對比靜態變量Method的聲明對應方法的位置與static代碼塊這三塊地方,可以發現,聲明順序與靜態代碼塊中反射獲取的順序一致,并且在各方法中執行反射出來的Method也是相等的。這里做到了提前加載被代理類的方法,然后使用到該代理類的方法時將被代理類的方法當參數傳到
h.invoke
中執行。
進入到被繼承的Proxy
中,發現在生成的類中使用的h
就是InvocationHandler
這個類。即我們在使用動態代理時所要實現才那個類!
所以這個地方是生成一個回調代理類的invoke
方法的類,來調用invoke
時決定什么時候執行被代理類的service
方法就能達到切面增強這個方法的效果
理解完了生成代碼的意義和處理流程,剩下的就是怎么構造這些代碼并將他編譯成.class文件
和被類加載器加載并被創建實例被我們所使用了。
如何構造和加載
參考Proxy.newProxyInstance
的代碼,看到getProxyClass0
,前面的代碼忽略,看注釋就知道這里是生成指定代理類的方法。直接點進去就好了。
記住這兩個變量分別是KeyFactory
和ProxyClassFactory
。然后回到proxyClassCache.get(loader, interfaces)
根據實現的接口數量來返回Key
往下走,最后指向的類都是Factory
,并在最后執行get
方法。
最后回到ProxyClassFactory
這個類的apply
方法。
最后在方法的底部發現ProxyGenerator.generateProxyClass
對應的作用就是構造相當于構造.java
源文件。
defineClass0
相當于javac
編譯成.class
文件并loadClass
返回對應的類
這個生成方法較復雜,經過簡單的查看源碼,已經知道步驟如下:
- 構造
.java
源文件 - 編譯成
.class
后加載類
筆者的方法比較簡單,直接使用freemaker
來構造源文件,需要傳入以下四個參數。
- package->生成類所在的包
- className->生成的代理類名稱
- interface->實現的接口類全類名
- methodList->需要重寫的方法列表
freemaker模板如下
package ${package};
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
* @Author: Jdragon
* @email: 1061917196@qq.com
* @Description: jdk動態代理實質
*/
public class ${className} extends Proxy implements ${interface} {
public ${className}(InvocationHandler h) {
super(h);
}
<#list 0..(methodList!?size-1) as i>
@Override
public final ${methodList[i].retType} ${methodList[i].methodName}(
<#list methodList[i].paramList as param>
${param} var${param_index}<#if param_has_next>,</#if>
</#list>) {
try {
<#if (methodList[i].retType!="void")>return (${methodList[i].retType})</#if>
<#if (methodList[i].paramList?size==0)>
super.h.invoke(this, m${i}, (Object[])null);
<#else>
super.h.invoke(this, m${i}, new Object[]{
<#list 0..(methodList[i].paramList!?size-1) as k>var${k}
<#if k_has_next>,</#if>
</#list>});
</#if>
} catch (RuntimeException | Error e) {
throw e;
}catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
</#list>
<#list 0..(methodList!?size-1) as i>
private static Method m${i};
</#list>
static{
try{
<#list 0..(methodList!?size-1) as i>
m${i} = Class.forName("${methodList[i].className}").getMethod("${methodList[i].methodName}"
<#list methodList[i].paramList as param>
,Class.forName("${param}")
</#list>);
</#list>
} catch (NoSuchMethodException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
主要代碼。以下代碼不完整,可到gitee獲取源碼
public class JdkProxyFactory {
private final static String LN = System.lineSeparator();
private final static AtomicInteger PROXY_INDEX = new AtomicInteger(0);
private final static Boolean SAVE_GENERATED_FILES = Boolean.valueOf(System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles"));
private final static String USER_DIR = System.getProperty("user.dir") + "/com/jdragon/proxy/";
private final static String PACKAGE_NAME = "com.jdragon.proxy";
public static Object newProxyInstance(ClassLoader classLoader,
@NotNull Class<?>[] interfaces,
@NotNull InvocationHandler h) {
try {
if (interfaces.length == 0) {
throw new Exception("至少要實現一個接口");
}
//使用被代理類的類名和自增數定義代理類的名字
String proxyClass = interfaces[0].getSimpleName() + "$Proxy" + PROXY_INDEX.incrementAndGet();
//加載代理類
Class<?> loadClass = loadClass(interfaces[0], proxyClass);
Constructor<?> constructor = loadClass.getDeclaredConstructor(InvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* @Description: 加載類
**/
private static Class<?> loadClass(Class<?> interfaces, String proxyClassName) throws Exception {
String classPath = PACKAGE_NAME + "." + proxyClassName;
//構建源代碼
String sourceCode = generateSourceCode(interfaces, proxyClassName);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
try (JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null))) {
List<JavaFileObject> files = Collections.singletonList(new MemoryJavaFileObject(proxyClassName, sourceCode));
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, files);
if (!task.call()) {
throw new Exception("任務調用異常");
}
ClassLoader classLoader = manager.getClassLoader(null);
Class<?> aClass = manager.getClassLoader(null).loadClass(classPath);
if (SAVE_GENERATED_FILES) {
save(proxyClassName, classLoader);
}
return aClass;
}
}
/**
* @Description: 構造源代碼
**/
private static String generateSourceCode(Class<?> interfaces, String proxyClassName) {
String interfaceName = interfaces.getName();
List<MethodEntity> methodEntities = new ArrayList<>();
methodEntities.add(new MethodEntity(Object.class, "toString", null,
String.class));
methodEntities.add(new MethodEntity(Object.class, "hashCode", null,
int.class));
methodEntities.add(new MethodEntity(Object.class, "equals", Collections.singletonList(Object.class.getName()),
boolean.class));
for (Method declaredMethod : interfaces.getDeclaredMethods()) {
MethodEntity methodEntity = new MethodEntity();
methodEntity.setClassName(interfaces);
methodEntity.setMethodName(declaredMethod.getName());
List<String> params = new ArrayList<>();
for (Parameter parameter : declaredMethod.getParameters()) {
String paramTypeName = parameter.getType().getName();
params.add(paramTypeName);
}
methodEntity.setParamList(params);
methodEntity.setRetType(declaredMethod.getReturnType());
methodEntity.setTransferType(declaredMethod.getReturnType());
methodEntities.add(methodEntity);
}
//利用定義好的模板傳入參數到freemaker進行遍歷填充,最后獲得源代碼
Map<String, Object> map = new HashMap<>(8);
map.put("package", PACKAGE_NAME);
map.put("className", proxyClassName);
map.put("interface", interfaceName);
map.put("methodList", methodEntities);
FreeMakerUtil freeMakerUtil = new FreeMakerUtil("/template/freemaker/", "ftl");
return freeMakerUtil.printString("proxy", map);
}
}