为哪些方法代理?
实现自己动态代理, 首先需要关注的点就是, 代理对象需要为哪些方法代理? 原生 JDK 的动态代理的实现是往上抽象出一层接口, 让目标对象和代理对象都实现这个接口, 怎么把接口的信息告诉 jdk 原生的动态代理呢? 如下代码所示, Proxy.newProxyInstance() 方法的第二个参数将接口的信息传递了进去第一个参数的传递进去一个类加载器, 在 jdk 的底层用它对比对象是否是同一个, 标准就是相同对象的类加载器是同一个
- ServiceInterface) Proxy.newProxyInstance(service.getClass().getClassLoader()
- , new Class[]{ServiceInterface.class}, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("前置通知");
- method.invoke(finalService,args);
- System.out.println("后置通知");
- return proxy;
- }
- });
我们也效仿它的做法. 代码如下:
- public class Test {
- public static void main(String[] args) {
- IndexDao indexDao = new IndexDao();
- Dao dao =(Dao) ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao));
- assert dao != null;
- System.out.println(dao.say("changwu"));
- }
- }
拿到了接口的 Class 对象后, 通过反射就得知了接口中有哪些方法描述对象 Method, 获取到的所有的方法, 这些方法就是我们需要增强的方法
如何将增强的逻辑动态的传递进来呢?
JDK 的做法是通过 InvocationHandler 的第三个参数完成, 他是个接口, 里面只有一个抽象方法如下: 可以看到它里面有三个入参, 分别是 代理对象, 被代理对象的方法, 被代理对象的方法的参数
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable;
- }
当我们使用 jdk 的动态代理时, 就是通过这个重写这个钩子函数, 将逻辑动态的传递进去, 并且可以选择在适当的地方让目标方法执行
InvocationHandler 接口必须存在必要性 1:
为什么不传递进去 Method, 而是传递进去 InvocationHandler 对象呢? 很显然, 我们的初衷是借助 ProxyUtil 工具类完成对代理对象的拼串封装, 然后让这个代理对象去执行 method.invoke(), 然而事与愿违, 传递进来的 Method 对象的确可以被 ProxyUtil 使用, 调用 method.invoke(), 但是我们的代理对象不能使用它, 因为代理对象在这个 ProxyUtil 还以一堆等待拼接字符串, ProxyUtil 的作用只能是往代理对象上叠加字符串, 却不能直接传递给它一个对象, 所以只能传递一个对象进来, 然后通过反射获取到这个对象的实例, 继而有可能实现 method.invoke()
InvocationHandler 接口必须存在必要性 2:
通过这个接口的规范, 我们可以直接得知回调方法的名字就是 invoke() 所以说, 在拼接字符串完成对代理对象的拼接时, 可以直接写死它
思路
我们需要通过上面的 ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao)) 方法完成如下几件事
根据入参位置的信息, 提取我们需要的信息, 如包名, 方法名, 等等
根据我们提取的信息通过字符串的拼接完成一个全新的 java 的拼接
这个 java 类就是我们的代理对象
拼接好的 java 类是一个 String 字符串, 我们将它写入磁盘取名 XXX.java
通过 ProxyUtil 使用类加载器, 将 XXX.java 读取 JVM 中, 形成 Class 对象
通过 Class 对象反射出我们需要的代理对象
ProxyUtil 的实现如下:
- public static Object newInstance(Class targetInf, MyInvocationHandler invocationHandler) {
- Method methods[] = targetInf.getDeclaredMethods();
- String line = "\n";
- String tab = "\t";
- String infName = targetInf.getSimpleName();
- String content = "";
- String packageContent = "package com.myproxy;" + line;
- // 导包, 全部导入接口层面, 换成具体的实现类就会报错
- //
- String importContent = "import" + targetInf.getName() + ";" + line
- + "import com.changwu. 代理技术. 模拟 jdk 实现动态代理. MyInvocationHandler;" + line
- + "import java.lang.reflect.Method;" + line
- + "import java.lang.Exception;" + line;
- String clazzFirstLineContent = "public class $Proxy implements" + infName +"{"+ line;
- String filedContent = tab + "private MyInvocationHandler handler;"+ line;
- String constructorContent = tab + "public $Proxy (MyInvocationHandler handler){" + line
- + tab + tab + "this.handler =handler;"
- + line + tab + "}" + line;
- String methodContent = "";
- // 遍历它的全部方法, 接口出现的全部方法进行增强
- for (Method method : methods) {
- String returnTypeName = method.getReturnType().getSimpleName(); method.getReturnType().getSimpleName());
- String methodName = method.getName();
- Class<?>[] parameterTypes = method.getParameterTypes();
- // 参数的. class
- String paramsClass = "";
- for (Class<?> parameterType : parameterTypes) {
- paramsClass+= parameterType.getName()+",";
- }
- String[] split = paramsClass.split(",");
- // 方法参数的类型数组 Sting.class String.class
- String argsContent = "";
- String paramsContent = "";
- int flag = 0;
- for (Class arg : parameterTypes) {
- // 获取方法名
- String temp = arg.getSimpleName();
- argsContent += temp + "p" + flag + ",";
- paramsContent += "p" + flag + ",";
- flag++;
- }
- // 去掉方法参数中最后面多出来的,
- if (argsContent.length()> 0) {
- argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
- paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
- }
- methodContent += tab + "public" + returnTypeName + "" + methodName +"("+ argsContent +") {" + line
- + tab + tab+"Method method = null;"+line
- + tab + tab+"String [] args0 = null;"+line
- + tab + tab+"Class<?> [] args1= null;"+line
- // invoke 入参是 Method 对象, 而不是上面的字符串, 所以的得通过反射创建出 Method 对象
- + tab + tab+"try{"+line
- // 反射得到参数的类型数组
- + tab + tab + tab + "args0 = \""+paramsClass+"\".split(\",\");"+line
- + tab + tab + tab + "args1 = new Class[args0.length];"+line
- + tab + tab + tab + "for (int i=0;i<args0.length;i++) {"+line
- + tab + tab + tab + "args1[i]=Class.forName(args0[i]);"+line
- + tab + tab + tab + "}"+line
- // 反射目标方法
- + tab + tab + tab + "method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\",args1);"+line
- + tab + tab+"}catch (Exception e){"+line
- + tab + tab+ tab+"e.printStackTrace();"+line
- + tab + tab+"}"+line
- + tab + tab + "return ("+returnTypeName+") this.handler.invoke(method,\" 暂时不知道的方法 \");" + line; //
- methodContent+= tab + "}"+line;
- }
- content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
- File file = new File("d:\\com\\myproxy\\$Proxy.java");
- try {
- if (!file.exists()) {
- file.createNewFile();
- }
- FileWriter fw = new FileWriter(file);
- fw.write(content);
- fw.flush();
- fw.close();
- // 将生成的. java 的文件编译成 .class 文件
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
- Iterable units = fileMgr.getJavaFileObjects(file);
- JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
- t.call();
- fileMgr.close();
- // 使用类加载器将. class 文件加载进 jvm
- // 因为产生的. class 不在我们的工程当中
- URL[] urls = new URL[]{new URL("file:D:\\\\")};
- URLClassLoader urlClassLoader = new URLClassLoader(urls);
- Class clazz = urlClassLoader.loadClass("com.myproxy.$Proxy");
- return clazz.getConstructor(MyInvocationHandler.class).newInstance(invocationHandler);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }
运行的效果:
- package com.myproxy;
- import com.changwu.myproxy.pro.Dao;
- import com.changwu.myproxy.pro.MyInvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.Exception;
- public class $Proxy implements Dao{
- private MyInvocationHandler handler;
- public $Proxy (MyInvocationHandler handler){
- this.handler =handler;
- }
- public String say(String p) {
- Method method = null;
- String [] args0 = null;
- Class<?> [] args1= null;
- try{
- args0 = "java.lang.String,".split(",");
- args1 = new Class[args0.length];
- for (int i=0;i<args0.length;i++) {
- args1[i]=Class.forName(args0[i]);
- }
- method = Class.forName("com.changwu.myproxy.pro.Dao").getDeclaredMethod("say",args1);
- }catch (Exception e){
- e.printStackTrace();
- }
- return (String) this.handler.invoke(method,"暂时不知道的方法");
- }
- }
解读
通过 newInstance() 用户获取到的代理对象就像上面的代理一样, 这个过程是在 java 代码运行时生成的, 但是直接看他的结果和静态代理差不错, 这时用户再去调用代理对象的 say(), 实际上就是在执行用户传递进去的 InvocationHandeler 里面的 invoke 方法, 但是亮点是我们把目标方法的描述对象 Method 同时给他传递进去了, 让用户可以执行目标方法 + 增强的逻辑
当通过反射区执行 Method 对象的 invoke() 方法时, 指定的哪个对象的当前方法呢? 这个参数其实是我们手动传递进去的代理对象代码如下
- public class MyInvocationHandlerImpl implements MyInvocationHandler {
- private Object obj;
- public MyInvocationHandlerImpl(Object obj) {
- this.obj = obj;
- }
- @Override
- public Object invoke(Method method, Object[] args) {
- System.out.println("前置通知");
- try {
- method.invoke(obj,args);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("后置通知");
- return null;
- }
- }
来源: https://www.cnblogs.com/ZhuChangwu/p/11648911.html