目录
环境:OpenJDK(Zulu 8.58.0.13-CA-macos-aarch64)
需求:我们常用AOP做各种切面业务,AOP的实现依赖于JDK动态代理和cglib,关于JDK动态代理的实现原理,我们可以自己做个简单实现来理解。
一、准备工作
首先提供出来简单的接口HelloService
,如下,
package com.szh.proxy.service;
public interface HelloService {
public void say();
public String say(String word, String mode);
}
及其实现类HelloServiceImpl
,如下,
package com.szh.proxy.service.impl;
import com.szh.proxy.service.HelloService;
public class HelloServiceImpl implements HelloService {
@Override
public void say() {
System.out.println("hello ");
}
@Override
public String say(String word, String mode) {
String result = "hello2 " + word + " " + mode;
System.out.println(result);
return result;
}
}
二、静态代理的简单实现
代理模式的意义无非就是,通过代理对象代替目标对象去做事,在此过程中可以对目标对象要做的事情进行业务增强。
这里通过实现接口HelloService
,并在成员变量中包含HelloService
类型的目标对象,即可简单代理,实现业务增强(在业务方法前后分别打印一些日志)。如下,是一个日志增强的代理类LogStaticProxy
,
package com.szh.proxy.jdkStatic;
import com.szh.proxy.service.HelloService;
/**
* 静态代理
*/
public class LogStaticProxy implements HelloService {
private HelloService targetObject;
public LogStaticProxy(HelloService targetObject) {
this.targetObject = targetObject;
}
@Override
public void say() {
System.out.println("我是 JDK 静态代理");
System.out.println("准备");
targetObject.say();
System.out.println("完成");
}
@Override
public String say(String word, String mode) {
System.out.println("我是 JDK 静态代理");
System.out.println("准备");
String result = targetObject.say(word, mode);
System.out.println("完成");
return result;
}
}
三、JDK动态代理的简单实现
针对静态代理的问题比较明显,LogStaticProxy
只代理了HelloService
这类目标对象。
倘若业务中有UserService
、OrderService
等也需要做这样的日志增强处理,可能需要改造静态代理类LogStaticProxy
了,让它动态化,做到对所有接口一律代理。
当然,我们可以使用JDK动态代理的代码实现模式,即利用接口InvocationHandler
和Proxy.newProxyInstance
做到。如下,
package com.szh.proxy.jdkDynamic;
import com.szh.proxy.service.HelloService;
import com.szh.proxy.service.impl.HelloServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HelloInterfaceServiceProxy implements InvocationHandler {
private Object target;
public HelloInterfaceServiceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是 JDK 动态代理");
Object result = null;
System.out.println("准备");
result = method.invoke(target, args);
System.out.println("完成");
return result;
}
public static void main(String[] args) {
// 目标对象
HelloService target = new HelloServiceImpl();
// 代理对象
Object proxyObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new HelloInterfaceServiceProxy(target));
HelloService proxy = (HelloService) proxyObj;
proxy.say();
proxy.say("2022", "happy");
}
}
这里如果不利用这些和其他第三方类库,如何自己实现。
根据静态代理类LogStaticProxy
的经验,设想要是能够做到对不同的目标接口,各自生成各自对应的一份LogStaticProxy
就达到动态化了。所以设想如下这样三步走:
- 自动生成动态代理类的java源文件
- 自动编译生成的源文件得到class字节码文件
- 加载字节码文件到JVM以生成代理对象
3.1、自动生成动态代理类的java源文件
第一步,目标比较简单,就是通过目标对象的信息,拼接对应代理类的源代码,形同LogStaticProxy.java
的内容。
分析其内容,大致包括package语句、import语句、代理类定义语句、成员变量定义语句、构造函数、目标接口的方法实现语句等。主要用到字符串拼接、反射和文件操作的基础知识。
3.2、自动编译生成的源文件得到class字节码文件
第二步,对第一步生成的源代码文件进行编译。主要用到jdk中JavaCompiler
的编译方式。
需要注意的是,JavaCompiler
存在于jdk而不存在于jre中,所以对一些运行环境只有jre的不可用。
3.3、加载字节码文件到JVM以生成代理对象
最后一步,利用类加载器加载第二步编译好的字节码文件,利用反射在JVM中创造出目标对象对应的代理对象实例。
3.4、JDK动态代理工具类
这里贴出简单日志增强静态代理类LogStaticProxy
的动态版,如下,
package com.szh.proxy.util;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* JDK动态代理工具类,简单模仿对接口的动态代理<br>
* 1、自动生成动态代理类的java源文件<br>
* 2、自动编译生成的源文件得到class字节码文件<br>
* 3、加载字节码文件到JVM生成代理对象
*/
public class ProxyUtil {
private static final String lineSeparator = System.getProperty("line.separator");
/**
* 三步走生成代理对象
* @param target 目标对象
* @return 代理对象
*/
public static Object newProxyInstance(Object target) throws IOException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// step1:生成java文件
String content = "";
/**
* package com.szh.proxy;
*/
String packageContent = "package com.szh.proxy;";
// 获取目标对象的接口,暂只考虑一个接口
Class targetInterface = target.getClass().getInterfaces()[0];
String targetInterfaceName = targetInterface.getName();
String targetInterfaceSimpleName = targetInterface.getSimpleName();
/**
* import com.szh.proxy.service.HelloService;
*/
String importContent = "import " + targetInterfaceName + ";";
String proxyClassName = "$Proxy"; // 注意代理类名需要保证唯一
String classContent = "public class " + proxyClassName + " implements " + targetInterfaceSimpleName + " {" + lineSeparator;
/**
* private HelloService targetObject;
*/
String fieldContent = "\tprivate " + targetInterfaceSimpleName + " targetObject;" + lineSeparator;
String constructContent = "\tpublic $Proxy(" + targetInterfaceSimpleName + " targetObject) {" + lineSeparator
+ "\t\tthis.targetObject = targetObject;" + lineSeparator
+ "\t}" + lineSeparator;
// 代理类中实现目标对象接口的源代码
String methodsContent = "";
Method[] declaredMethods = targetInterface.getDeclaredMethods();
/**
* @Override
* public void say(String word) {
* System.out.println("我是 JDK 静态代理");
* System.out.println("准备");
* targetObject.say(word);
* System.out.println("完成");
* }
*/
for (Method method : declaredMethods) {
String methodName = method.getName(); // 方法名
Class returnType = method.getReturnType(); // 返回类型
Class[] parameterTypes = method.getParameterTypes();
String argsContent = "";
String argsNameContent = "";
int i = 0;
for (Class paramType : parameterTypes) {
String paramTypeName = paramType.getSimpleName();
String argName = "arg" + i; // 每个形参名
argsContent += (paramTypeName + " " + argName + ", "); // 方法形参列表
argsNameContent += (argName + ", "); // 实参名列表
i++;
}
if (i > 0) {
argsContent = argsContent.substring(0, argsContent.lastIndexOf(", "));
argsNameContent = argsNameContent.substring(0, argsNameContent.lastIndexOf(", "));
}
String invokeContent = "";
String resultContent = "";
if (Void.TYPE.equals(returnType)) {
invokeContent = "\ttargetObject." + methodName + "(" + argsNameContent + ");";
} else {
invokeContent = "\t" + returnType.getSimpleName() + " result = targetObject." + methodName + "(" + argsNameContent + ");";
resultContent = "return result;";
}
methodsContent += "\t@Override" + lineSeparator
+ "\tpublic " + returnType.getSimpleName() + " " + methodName + "(" + argsContent + ") {" + lineSeparator
+ "\t\tSystem.out.println(\"我是 JDK 动态代理\");" + lineSeparator // 代理逻辑,简单实现先暂时写死
+ "\t\tSystem.out.println(\"准备\");" + lineSeparator
+ "\t" + invokeContent + lineSeparator
+ "\t\tSystem.out.println(\"完成\");" + lineSeparator;
if (Void.TYPE.equals(returnType)) {
methodsContent += "\t}" + lineSeparator;
} else {
methodsContent += "\t\t" + resultContent + lineSeparator;
methodsContent += "\t}" + lineSeparator;
}
}
content = packageContent + lineSeparator
+ importContent + lineSeparator
+ classContent + lineSeparator
+ fieldContent + lineSeparator
+ constructContent + lineSeparator
+ methodsContent + lineSeparator
+ "}";
String sourceFilePath = "/Users/songzehao/Desktop/com/szh/proxy/" + proxyClassName + ".java";
File sourceFile = new File(sourceFilePath);
File dir = new File("/Users/songzehao/Desktop/com/szh/proxy");
if (!dir.exists()) dir.mkdirs();
if (!sourceFile.exists()) sourceFile.createNewFile();
FileWriter fileWriter = new FileWriter(sourceFile);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
// step2:编译java文件(使用jdk的api,开发环境可用,但运行环境只有jre,不可用)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.CHINA, Charset.defaultCharset());
Iterable iterable = fileManager.getJavaFileObjects(sourceFile);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
// step3:加载class文件,返回代理对象
URL[] urls = new URL[] {
new URL("file:///Users/songzehao/Desktop/")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class clazz = classLoader.loadClass("com.szh.proxy.$Proxy");
// $Proxy没有无参构造,不使用clazz.newInstance(),要使用含参构造
Constructor constructor = clazz.getDeclaredConstructor(targetInterface);
Object proxyInstance = constructor.newInstance(target);
return proxyInstance;
}
}
3.5、解耦代理逻辑
上面的ProxyUtil
因为未将代理逻辑解耦出来,如若以后除了这样的简单日志处理,还有别的切面代理业务,可能需要重写一份ProxyUtil
,再修改里面写死的代理逻辑,不够灵活。
所以使用处理器handler
的方式将代理逻辑抽离出来。
3.5.1、代理逻辑处理器
定义代理逻辑处理器接口InvokeHandler
,
package com.szh.proxy.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public interface InvokeHandler {
Object getTarget(); // 返回目标对象
Object invoke(Method method, Object... args) throws InvocationTargetException, IllegalAccessException;
}
实现简单日志切面功能的代理逻辑处理器LogHandler
,
package com.szh.proxy.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 增强日志处理器,内含了目标对象、调用详情(方法、参数、代理逻辑)
*/
public class LogHandler implements InvokeHandler {
private Object target; // 目标对象
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object getTarget() {
return target;
}
/**
* 在此添加调用目标对象的业务方法并添加增强日志处理
* @param method 目标对象的业务方法
* @param args 目标对象业务方法的参数
* @return 目标对象方法返回值
*/
@Override
public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("我也是 JDK 动态代理");
System.out.println("准备");
Object result = method.invoke(target, args);
System.out.println("完成");
return result;
}
}
这样解耦出来,对其他切面逻辑的处理,只需要像上面这样实现即可。
3.5.2、解耦后的JDK动态代理工具类
对ProxyUtil
解耦改造为ProxyInvokeUtil
,
package com.szh.proxy.util;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* JDK动态代理工具类,在{@link com.szh.proxy.util.ProxyUtil}基础上,换种handler的方式实现invoke,
* 以此将增强业务的处理和工具类{@link com.szh.proxy.util.ProxyUtil}解耦,
* 因为{@link com.szh.proxy.util.ProxyUtil}中的增强业务或代理逻辑是写死的,不够灵活,
* 这样以后若有别的切面业务,只需要写一个新切面的处理器如{@link com.szh.proxy.util.LogHandler}即可,
* 就不用再写一个{@link com.szh.proxy.util.ProxyUtil},然后替换代理逻辑了。
*/
public class ProxyInvokeUtil {
private static final String lineSeparator = System.getProperty("line.separator");
/**
* 三步走生成代理对象
* @param invokeHandler 增强处理器(已经内含了目标对象)
* @return 代理对象
*/
public static Object newProxyInstance(InvokeHandler invokeHandler) throws IOException, ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// step1:生成java文件
String content = "";
/**
* package com.szh.proxy;
*/
String packageContent = "package com.szh.proxy;";
// 获取目标对象的接口,暂只考虑一个接口
Object target = invokeHandler.getTarget();
Class targetInterface = target.getClass().getInterfaces()[0];
String targetInterfaceName = targetInterface.getName();
String targetInterfaceSimpleName = targetInterface.getSimpleName();
/**
* import com.szh.proxy.util.InvokeHandler;
* import com.szh.proxy.service.HelloService;
* import java.lang.reflect.Method;
*/
String importContent = "import com.szh.proxy.util.InvokeHandler;" + lineSeparator
+ "import " + targetInterfaceName + ";" + lineSeparator
+ "import java.lang.reflect.Method;";
String proxyClassName = "$Proxy"; // 注意代理类名需要保证唯一
String classContent = "public class " + proxyClassName + " implements " + targetInterfaceSimpleName + " {" + lineSeparator;
/**
* private InvokeHandler invokeHandler;
*/
String fieldContent = "\tprivate InvokeHandler invokeHandler;" + lineSeparator;
String constructContent = "\tpublic $Proxy(InvokeHandler invokeHandler) {" + lineSeparator
+ "\t\tthis.invokeHandler = invokeHandler;" + lineSeparator
+ "\t}" + lineSeparator;
// 代理类中实现目标对象接口的源代码
String methodsContent = "";
Method[] declaredMethods = targetInterface.getDeclaredMethods();
/**
* @Override
* public void say(String word) {
* // 通过业务接口获取该方法里对应的method
* Method method = HelloService.class.getDeclaredMethod("say");
* invokeHandler.invoke(method, word);
* }
*/
for (Method method : declaredMethods) {
String methodName = method.getName(); // 方法名
Class returnType = method.getReturnType(); // 返回类型
Class[] parameterTypes = method.getParameterTypes();
String argsContent = "";
String argsNameContent = "";
String classTypesContent = "";
int i = 0;
for (Class paramType : parameterTypes) {
String paramTypeName = paramType.getSimpleName();
String argName = "arg" + i; // 每个形参名
argsContent += (paramTypeName + " " + argName + ", "); // 方法形参列表
argsNameContent += (argName + ", "); // 实参名列表
classTypesContent += paramType.getName() + ".class, ";
i++;
}
if (i > 0) {
argsContent = argsContent.substring(0, argsContent.lastIndexOf(", "));
argsNameContent = argsNameContent.substring(0, argsNameContent.lastIndexOf(", "));
classTypesContent = classTypesContent.substring(0, classTypesContent.lastIndexOf(", "));
} else {
// 方法无参
argsNameContent = "(Object[]) null";
classTypesContent = "(Class[]) null";
}
// 暂时catch所有异常
String tryContent = "\t\ttry {";
String catchContent = "\t\t} catch (Exception e) {" + lineSeparator + "\t\t\tSystem.err.println(e);" + lineSeparator + "\t\t}";
String invokeContent = "\t\t\tMethod method = " + targetInterfaceSimpleName + ".class.getDeclaredMethod(\""
+ methodName + "\", " + classTypesContent + ");" + lineSeparator;
if (!Void.TYPE.equals(returnType)) {
invokeContent = tryContent + lineSeparator + invokeContent;
invokeContent = "\t\t" + returnType.getSimpleName() + " result = null;" + lineSeparator + invokeContent;
invokeContent += "\t\t\tresult = (" + returnType.getSimpleName() + ") invokeHandler.invoke(method, " + argsNameContent + ");" + lineSeparator;
invokeContent += catchContent + lineSeparator;
invokeContent += "\t\treturn result;" + lineSeparator;
} else {
invokeContent = tryContent + lineSeparator + invokeContent;
invokeContent += "\t\t\t" + "invokeHandler.invoke(method, " + argsNameContent + ");" + lineSeparator;
invokeContent += catchContent + lineSeparator;
}
methodsContent += "\t@Override" + lineSeparator
+ "\tpublic " + returnType.getSimpleName() + " " + methodName + "(" + argsContent + ") {" + lineSeparator
+ invokeContent + "\t}" + lineSeparator;
}
content = packageContent + lineSeparator
+ importContent + lineSeparator
+ classContent + lineSeparator
+ fieldContent + lineSeparator
+ constructContent + lineSeparator
+ methodsContent + lineSeparator
+ "}";
String sourceFilePath = "/Users/songzehao/Desktop/com/szh/proxy/" + proxyClassName + ".java";
File sourceFile = new File(sourceFilePath);
File dir = new File("/Users/songzehao/Desktop/com/szh/proxy");
if (!dir.exists()) dir.mkdirs();
if (!sourceFile.exists()) sourceFile.createNewFile();
FileWriter fileWriter = new FileWriter(sourceFile);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
// step2:编译java文件(使用jdk的api,开发环境可用,但运行环境只有jre,不可用)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.CHINA, Charset.defaultCharset());
Iterable iterable = fileManager.getJavaFileObjects(sourceFile);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
// step3:加载class文件,返回代理对象
URL[] urls = new URL[] {
new URL("file:///Users/songzehao/Desktop/")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class clazz = classLoader.loadClass("com.szh.proxy.$Proxy");
// $Proxy没有无参构造,不使用clazz.newInstance(),要使用含参构造
Constructor constructor = clazz.getDeclaredConstructor(InvokeHandler.class);
Object proxyInstance = constructor.newInstance(invokeHandler);
return proxyInstance;
}
}
为了主线思路简洁起见,未处理很多优化点,如对目标对象指定接口进行代理,以及生成的代理类对返回值为基本类型或自定义类型的处理,以及对异常的处理等等。
3.6、测试一下
写个测试方法跑一下,
package com.szh.proxy;
import com.szh.proxy.service.HelloService;
import com.szh.proxy.service.impl.HelloServiceImpl;
import com.szh.proxy.util.LogHandler;
import com.szh.proxy.util.ProxyInvokeUtil;
import com.szh.proxy.util.ProxyUtil;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
public class TestMyProxy {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, InstantiationException, IllegalAccessException {
HelloService targetObject = new HelloServiceImpl();
LogHandler logHandler = new LogHandler(targetObject);
// HelloService proxyInstance = (HelloService) ProxyUtil.newProxyInstance(targetObject);
HelloService proxyInstance = (HelloService) ProxyInvokeUtil.newProxyInstance(logHandler);
proxyInstance.say();
proxyInstance.say("2022", "happy");
}
}
下面输出符合预期,
我也是 JDK 动态代理
准备
hello
完成
我也是 JDK 动态代理
准备
hello2 2022 happy
完成
再查看下动态生成的$Proxy
代理类文件,
查看一下解耦后的$Proxy.java
的内容,
package com.szh.proxy;
import com.szh.proxy.util.InvokeHandler;
import com.szh.proxy.service.HelloService;
import java.lang.reflect.Method;
public class $Proxy implements HelloService {
private InvokeHandler invokeHandler;
public $Proxy(InvokeHandler invokeHandler) {
this.invokeHandler = invokeHandler;
}
@Override
public void say() {
try {
Method method = HelloService.class.getDeclaredMethod("say", (Class[]) null);
invokeHandler.invoke(method, (Object[]) null);
} catch (Exception e) {
System.err.println(e);
}
}
@Override
public String say(String arg0, String arg1) {
String result = null;
try {
Method method = HelloService.class.getDeclaredMethod("say", java.lang.String.class, java.lang.String.class);
result = (String) invokeHandler.invoke(method, arg0, arg1);
} catch (Exception e) {
System.err.println(e);
}
return result;
}
}
以上。