静态代理和动态代理
实现静态代理
我们首先实现一个静态代理之后再进行分析。
-
创建一个接口Animal
public interface Animal { // 用来定义动物的行为 void action(); }
-
实现一个Cat类并继承Animal接口
public class Cat implements Animal { @Override public void action() { System.out.println("猫会叫..."); } }
-
创建一个代理类来静态代理动物的行为。分析阶段会介绍为什么这叫静态的。
// 代理类 public class AnimalProxy implements Animal { Cat cat; // 在构造函数中传入cat,以便后面使用cat中的方法 public AnimalProxy(Cat cat) { this.cat = cat; } @Override public void action() { // 在执行方法之前需要执行的操作 System.out.println("静态代理开始..."); cat.action(); // 在执行方法之后需要执行的操作 System.out.println("静态代理结束..."); } }
-
测试静态代理是否生效
public class Test { public static void main(String[] args) { Cat cat = new Cat(); AnimalProxy proxy = new AnimalProxy(cat); proxy.action(); } }
小结
静态代理是通过同样继承一个接口,在实现目标类方法的基础上,再做扩展。如下图所示。
静态代理的优化思路
通过上述的实验我们可以得出静态代理的缺点:
静态代理的缺点:
- 会产生很多的代理类
- 产生的代理类只能代理既定的接口
如果需要优化静态代理,我们就需要可以动态的修改继承的接口。例如不再固定的继承Animal接口,才能完成代理。
并且我们希望可以不需要每代理一个类就需要编写一个代理类来进行代理。例如当再增加一个类Dog的时候我们不需要再编写一个DogProxy
动态代理
尝试使用JDK的动态代理
-
首先我们编写一个动态代理的接口类
public interface Animal { void action(String name); }
-
编写目标类继承Animal类,并实现自己的逻辑
public class Cat implements Animal { @Override public void action(String name) { System.out.println(name + "is running"); } }
-
编写一个自定义的InvocationHandler代理类来代理Cat目标类的执行逻辑
public class CustomInvocationHandler implements InvocationHandler { Object target; public CustomInvocationHandler(Object object) { this.target = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理开始..."); Object invoke = method.invoke(target, args); System.out.println("动态代理结束..."); return invoke; } }
-
测试动态代理
public class Test { public static void main(String[] args) { // 将这一选项设置为true之后会在根目录下生成一个class类文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 实现我们自定义的InvocationHandler,并将我们需要代理的带传入Handler中 CustomInvocationHandler handler = new CustomInvocationHandler(new Cat()); // 使用Proxy.newProxyInstance()需要传入三个参数 // 1. 要使用的类加载器,这里我们传入Test类加载器即可 // 2. 我们需要传入一个Class数组,其中放入我们目标类继承的接口(也就是我们需要动态代理的方法) // 3. 传入我们自定义的InvocationHandler Animal animalProxy = (Animal) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Animal.class}, handler); // 使用action()方法 animalProxy.action("小猫"); } }
总结一下动态代理
1. 让目标类和代理类继承同一个接口 2. 使用Proxy.newProxyInstance()方法来进行动态代理
这样的好处在于我们不再限制需要继承的接口,我们只需要在方法中传入被继承的接口即可
同时我们不会再编写许多的动态代理类来对目标对象进行代理,只需要一个自定义一个InvocationHandler就可以处理一种逻辑
自己实现动态代理
在了解了JDK的动态代理之后,我们尝试更加深入了解如何自己实现动态代理。
大致思路:
- 生成一个.java文件
- 需要生成对应的package包名
- 生成对应的import文件
- 生成对应的类
- 生成需要被我们代理的方法
- 将.java文件存储到本地磁盘中
- 将.java文件编译成.class文件
- 动态加载生成的类并实例化
-
实现自定义的 RealizeInvocationHandler,作用类似于JDK动态代理中的 InvocationHandler
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public interface RealizeInvocationHandler { Object invoke(Object proxy, Method method, Object... args) throws InvocationTargetException, IllegalAccessException; }
-
实现动态代理类,作用类似JDK的Proxy类,其中的createProxy()方法类似Proxy中的newProxyInstance()方法。
import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; public class CustomProxy { /** * 他能返回一个对象--符合我们预期的代理对象 * io把我们的代码写进一个.java文件中,然后再手动把这个.java文件进行编译 * 编译完成之后会生成一个class文件,继而把这个class文件加载到JVM中 * 再通过反射区实例化这个对象,最终返回出去 */ public static Object createProxy(Class target, RealizeInvocationHandler invocationHandler) { // 用于存储生成的方法区的字符串 String methodSrc = ""; // win下的换行符 String rt = "\r\n"; // 制表符 String tab = "\t"; // 获取接口当中的所有方法,方便后面便利方法构建代理类的字符串 Method[] methods = target.getMethods(); // 遍历所有方法 for (Method method : methods) { // 用于存储生成的标记参数 StringBuilder methodParametersStr = new StringBuilder(); // 用于存储生成的参数值 StringBuilder argsValueStr = new StringBuilder(); // 用于标记获取getMethod() 方法参数的 StringBuilder getMethodParamStr = new StringBuilder(); // 判断这个方法的返回值是不是void类型 boolean isVoid = false; // 获取方法返回值的名称,如果是void String returnTypeSimpleName = method.getReturnType().getSimpleName(); if ("void".equals(returnTypeSimpleName)) { isVoid = true; } // 如果参数大于0,则处理后加入其中 if (method.getParameterCount() > 0) { // 获取方法所有的参数 Parameter[] parameters = method.getParameters(); /* 获取参数类型名称,并在之后参数之后加上val和',' */ for (int i = 0; i < parameters.length; i++) { // 用来获取参数类型 String parameterTypeSimpleName = parameters[i].getType().getSimpleName(); // 这一段相当于是生成方法参数的内容,如果我们代理的方法需要传入参数,即传入: (String var1,int var2,...)以此类推 methodParametersStr.append(parameterTypeSimpleName).append(" var").append(i).append(","); argsValueStr.append("var").append(i).append(","); /* 我们动态代理的本质其实是调用 Class.class.getMethod() 方法,来获取到其中某个方法并调用invoke()来进行动态代理 例如: Method md = Animal.class.getMethod("action",String.class,int.class); h.invoke(this, md, new Object[]{var0,var1}); 所以这里是在生成 getMethod() 方法后面的参数 */ getMethodParamStr.append(parameterTypeSimpleName).append(".class").append(","); } // 去除字符串后面最后一个',' methodParametersStr.deleteCharAt(methodParametersStr.length()-1); argsValueStr.deleteCharAt(argsValueStr.length()-1); getMethodParamStr.deleteCharAt(getMethodParamStr.length()-1); } // 在上面完成了所有需求收集之后,开始生成一个完整的方法 methodSrc += tab + "@Override" + rt + tab + "public " + returnTypeSimpleName + " " + method.getName() + "("+ methodParametersStr +") {" + rt + tab + tab + " try {" + rt + tab + tab + tab + "Method md = " + target.getSimpleName() + ".class.getMethod(\"" + method.getName() + "\"" + (method.getParameterCount() == 0?"":"," + getMethodParamStr) + ");" + rt + tab + tab + tab + (isVoid?"":"return ("+returnTypeSimpleName+") ") + "h.invoke(this, md, new Object[]{" + argsValueStr + "});" + rt + tab + tab + "} catch (Exception e) {" + rt + tab + tab + tab + "e.printStackTrace();" + rt + tab + tab + "}" + rt + tab + (isVoid?"}":"return null;" + rt + tab + "}") + rt; } /* 生成完整的类 特别注意,这里导入的 RealizeInvocationHandler 是我们自己定义和实现的。类似JDK动态代理中的 InvocationHandler */ String src = "package com.spring2.proxy.builderCode;" + rt + rt + "import java.lang.reflect.Method;" + rt + "import " + target.getName() + ";" + rt + "import com.spring2.proxy.dynamicProxy.RealizeInvocationHandler;" + rt + "public class $Proxy1 implements " + target.getSimpleName() + "{" + rt + tab + "RealizeInvocationHandler h;" + rt + tab + "public $Proxy1(RealizeInvocationHandler h) {" + rt + tab + tab + "this.h = h;" + rt + tab + "}" + rt + methodSrc + rt + "}"; // 选择我们要存储的文件路径!!!注意这里需要自己选择自己的文件路径 String filePath = "H:\\sources\\zilu\\spring-framework-5.1.x\\spring-zes\\src\\main\\java\\com\\spring2\\proxy\\builderCode\\$Proxy1.java"; // 生成了java文件之后,通过IO将其输出到指定文件夹 FileWriter fw = null; try { //创建字符输出流对象,负责向文件内写入 fw = new FileWriter(filePath); //将str里面的内容读取到fw所指定的文件中 fw.write(src); } catch (IOException e) { e.printStackTrace(); }finally{ if(fw!=null){ try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } } // 通过动态编译将我们刚刚生成的Java文件编译成class文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 获取文件管理器 参数依次为:错误监听器,区域对象,编码 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 通过文件全路径获取要编译的文件对象 Iterable<? extends JavaFileObject> javaFileObjectsFromStrings = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(filePath)); // 创建编译任务,参数依次为:错误输出流,文件管理器,错误处理器,编译器选项,参与编译的class,待编译的java文件 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, javaFileObjectsFromStrings); // 执行任务 Boolean call = task.call(); if (call) { System.out.println("编译成功~"); } // 动态加载类 URL[] urls = new URL[0]; try { urls = new URL[]{new URL("file:/H:\\sources\\zilu\\spring-framework-5.1.x\\spring-zes\\src\\main\\java")}; } catch (MalformedURLException e) { e.printStackTrace(); } URLClassLoader ul = new URLClassLoader(urls); Class<?> c = null; try { c = ul.loadClass("com.spring2.proxy.builderCode.$Proxy1"); // 实例化对象c并返回 Constructor<?> declaredConstructor = c.getDeclaredConstructor(RealizeInvocationHandler.class); Object proxy = declaredConstructor.newInstance(invocationHandler); return proxy; } catch (Exception e) { e.printStackTrace(); } return null; } }
-
测试我们的动态代理
RealizeInvocationHandlerImpl
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class RealizeInvocationHandlerImpl implements RealizeInvocationHandler { Object target; public RealizeInvocationHandlerImpl(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object... args) throws InvocationTargetException, IllegalAccessException { System.out.println("自实现动态代理开始"); Object invoke = method.invoke(target, args); System.out.println("自实现动态代理结束"); return invoke; } }
Main方法
public static void main(String[] args) { RealizeInvocationHandler handler = new RealizeInvocationHandlerImpl(new Cat()); Animal proxy = (Animal) CustomProxy.createProxy(Animal.class, handler); if (proxy != null) { proxy.action("zes ", 18); } else { System.out.println("自实现动态代理失败"); } }