- 一、准备工作
- 二、静态代理的简单实现
- 三、JDK动态代理的简单实现
- 3.1、自动生成动态代理类的java源文件
- 3.2、自动编译生成的源文件得到class字节码文件
- 3.3、加载字节码文件到JVM以生成代理对象
- 3.4、JDK动态代理工具类
- 3.5、解耦代理逻辑
- 3.5.1、代理逻辑处理器
- 3.5.2、解耦后的JDK动态代理工具类
- 3.6、测试一下
环境: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以生成代理对象
第一步,目标比较简单,就是通过目标对象的信息,拼接对应代理类的源代码,形同LogStaticProxy.java的内容。
分析其内容,大致包括package语句、import语句、代理类定义语句、成员变量定义语句、构造函数、目标接口的方法实现语句等。主要用到字符串拼接、反射和文件 *** 作的基础知识。
第二步,对第一步生成的源代码文件进行编译。主要用到jdk中JavaCompiler的编译方式。
需要注意的是,JavaCompiler存在于jdk而不存在于jre中,所以对一些运行环境只有jre的不可用。
最后一步,利用类加载器加载第二步编译好的字节码文件,利用反射在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; public class ProxyUtil { private static final String lineSeparator = System.getProperty("line.separator"); public static Object newProxyInstance(Object target) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // step1:生成java文件 String content = ""; String packageContent = "package com.szh.proxy;"; // 获取目标对象的接口,暂只考虑一个接口 Class targetInterface = target.getClass().getInterfaces()[0]; String targetInterfaceName = targetInterface.getName(); String targetInterfaceSimpleName = targetInterface.getSimpleName(); String importContent = "import " + targetInterfaceName + ";"; String proxyClassName = "$Proxy"; // 注意代理类名需要保证唯一 String classContent = "public class " + proxyClassName + " implements " + targetInterfaceSimpleName + " {" + lineSeparator; String fieldContent = "tprivate " + targetInterfaceSimpleName + " targetObject;" + lineSeparator; String constructContent = "tpublic $Proxy(" + targetInterfaceSimpleName + " targetObject) {" + lineSeparator + "ttthis.targetObject = targetObject;" + lineSeparator + "t}" + lineSeparator; // 代理类中实现目标对象接口的源代码 String methodsContent = ""; Method[] declaredMethods = targetInterface.getDeclaredMethods(); 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 + "ttSystem.out.println("我是 JDK 动态代理");" + lineSeparator // 代理逻辑,简单实现先暂时写死 + "ttSystem.out.println("准备");" + lineSeparator + "t" + invokeContent + lineSeparator + "ttSystem.out.println("完成");" + lineSeparator; if (Void.TYPE.equals(returnType)) { methodsContent += "t}" + lineSeparator; } else { methodsContent += "tt" + 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的方式将代理逻辑抽离出来。
定义代理逻辑处理器接口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; } @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; public class ProxyInvokeUtil { private static final String lineSeparator = System.getProperty("line.separator"); public static Object newProxyInstance(InvokeHandler invokeHandler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // step1:生成java文件 String content = ""; String packageContent = "package com.szh.proxy;"; // 获取目标对象的接口,暂只考虑一个接口 Object target = invokeHandler.getTarget(); Class targetInterface = target.getClass().getInterfaces()[0]; String targetInterfaceName = targetInterface.getName(); String targetInterfaceSimpleName = targetInterface.getSimpleName(); 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; String fieldContent = "tprivate InvokeHandler invokeHandler;" + lineSeparator; String constructContent = "tpublic $Proxy(InvokeHandler invokeHandler) {" + lineSeparator + "ttthis.invokeHandler = invokeHandler;" + lineSeparator + "t}" + lineSeparator; // 代理类中实现目标对象接口的源代码 String methodsContent = ""; Method[] declaredMethods = targetInterface.getDeclaredMethods(); 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 = "tttry {"; String catchContent = "tt} catch (Exception e) {" + lineSeparator + "tttSystem.err.println(e);" + lineSeparator + "tt}"; String invokeContent = "tttMethod method = " + targetInterfaceSimpleName + ".class.getDeclaredMethod("" + methodName + "", " + classTypesContent + ");" + lineSeparator; if (!Void.TYPE.equals(returnType)) { invokeContent = tryContent + lineSeparator + invokeContent; invokeContent = "tt" + returnType.getSimpleName() + " result = null;" + lineSeparator + invokeContent; invokeContent += "tttresult = (" + returnType.getSimpleName() + ") invokeHandler.invoke(method, " + argsNameContent + ");" + lineSeparator; invokeContent += catchContent + lineSeparator; invokeContent += "ttreturn result;" + lineSeparator; } else { invokeContent = tryContent + lineSeparator + invokeContent; invokeContent += "ttt" + "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; } }
以上。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)