手写JDK动态代理

    xiaoxiao2022-07-07  198

    这里说手写,其实也就是去掉了很多JDK动态代理实现的细节的,只是将核心的思想抽丝剥茧出来,用自己的,简单的方式实现了JDK动态代理。

     

    再此我要声明几点:

    1.该篇博客是需要你对JDK动态代理有一定了解的。

    2.JDK代理的实现远比此要复杂与精巧。

     

    下面,我们就来看看代码与解析:

     

     

    关于下面将干的事情,先罗列在下面这张表中,以便有个大致的认识:

    关于手写动态代理的几点说明: 1.我们主体是模仿JDK动态代理来完成的。 2.但是我们不会使用JDK动态代理或者其它动态代理提供的任何组件。 3.手写Proxy工具类(JDK动态代理中使用Proxy.newProxyInstance(..)方法来完成对代理对象的创建) 4.我们都知道JDK的动态代理需要三个参数 1>classLoader 2>目标类所实现的接口 3>InvocationHandler实现类 5.自定义类加载器。 6.接口们可以直接从传入对象的类对象获取,所以和以前一样。 7.需要自己手写实现InvocationHandler的实现类。 8.我们模仿JDK动态代理生成的代理对象的源码,手写我们的动态代理生成的对象。 9.将我们生成的代理类手动的装载到JVM中执行。 10.在特定的时刻手动删除生成的代理类的文件和代理类被Javac编译后生成的.class文件。

     

    首先,我们来完成对SDPProxyUtil工具类的实现(可类比为JDK动态代理中的Proxy类)

     

    /** ClassName:SDPProxyUtil */ //自定义的代理生成工具 public class SDPProxyUtil implements Serializable { private static final String ln = "\r\n"; /** * * @param loader:类加载器,我们将传入我们自定义的类加载器 * @param interfaces:被代理对象所实现的所有接口 * @param h:方法调用拦截处理器 * @return */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, SDPInvocationHandler h) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { /** * 1.通过手写生成源代码 */ String source = getProxySource(interfaces); /** * 2.将生成的源代码保存到磁盘(.java格式) */ String path = SDPProxyUtil.class.getResource("").getPath(); File file = new File(path + "$Proxy0.java"); FileOutputStream fos = new FileOutputStream(file); byte[] bytes = source.getBytes(); fos.write(bytes); fos.close(); /** * * 3.将磁盘上的.java文件通过自定义的类加载器编译成.class文件 */ //使用JDK自带的工具类获取我们的Javac编译器; JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); //标准的文件管理 StandardJavaFileManager standardFileManager = javaCompiler.getStandardFileManager(null, null, null); //对指定文件进行编译 Iterable javaFileObjects = standardFileManager.getJavaFileObjects(file); //创建编译任务 JavaCompiler.CompilationTask task = javaCompiler.getTask(null,standardFileManager,null,null,null,javaFileObjects); //开启任务 task.call(); //关闭管理器 standardFileManager.close(); /** * 4.将.class文件动态加载进虚拟机中 */ Class<?> proxyClazz = loader.loadClass(file.getName().replace("java","class")); /** * 5.返回被代理后的结果对象 */ //使用反射获取构造器创建代理对象实例 Constructor<?> constructor = proxyClazz.getConstructor(new Class<?>[]{Person.class}); Person proxyPerson = (Person) constructor.newInstance(h.getTarget()); //删除生成的.java文件 file.delete(); return proxyPerson; } //手写.java代码,注意一定要细心,否则很容易出错 private static String getProxySource(Class<?>[] interfaces){ StringBuffer src = new StringBuffer(); src.append("package com.sdp.DoProxy;" + ln); src.append("import java.lang.reflect.Method;" + ln); src.append("public class $Proxy0 extends SDPInvocationHandler implements "); //遍历接口使代理类实现当前所有接口 for(int i = 0;i<interfaces.length;i++){ if(i<interfaces.length-1){ src.append(interfaces[i].getName()+","); }else{ src.append(interfaces[i].getName()+"{" + ln); } } src.append("public $Proxy0(Person target) {" + ln); src.append("super(target);" + ln); src.append("}" + ln); //遍历当前所有接口,为所有接口中的所有方法生成一个代理方法 for(Class<?> clazz : interfaces){ for (Method m : clazz.getMethods()) { src.append("public final " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln); src.append("try{" + ln); src.append("Method m = " + clazz.getName() + ".class.getMethod(\"" +m.getName()+"\",new Class[]" + Arrays.toString(m.getParameterTypes()).replace('[','{').replace(']','}')+ ");" + ln); src.append("Object result = this.invoke(this,m,new Object[]{"); for(int i =0 ;i < m.getParameterCount() ;i++){ if(i<m.getParameterCount()-1){ src.append("var"+i+","); }else{ src.append("var"+i); } } src.append("});"+ln); if(!m.getReturnType().getName().contains("void")){ src.append("return ("+m.getReturnType().getName()+")result;"); } src.append("}catch(Throwable e){e.printStackTrace();}" + ln); if(!m.getReturnType().getName().contains("void")){ src.append("return null;"+ln); } src.append("}" + ln); } } src.append("}"+ln); return src.toString(); } }

     

     

    然后,我们来完成对SDPInvocationHandler类的完成(实现了InvocationHandler接口)

     

    /** ClassName:SDPInvocationHandler */ //自定义的方法拦截处理器 public class SDPInvocationHandler implements InvocationHandler { //将要被代理的对象 private Person target; public Person getTarget() { return target; } //传入将要被代理的对象 public SDPInvocationHandler(Person target) { this.target = target; } /** * @param proxy:代理对象 * @param method:当前调用的方法 * @param args:当前调用方法的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //根据不同的方法实现不同的增强方式 if(method.getName().equals("say")){ String result = (String) method.invoke(target,args); return "前置增强>"+result+"<后置增强"; } if(method.getName().equals("eat")){ System.out.println("前置增强!"); method.invoke(target,args); System.out.println("后置增强!"); } return null; } }

     

     

    接下来,对自定义类加载器SDPClassLoader的实现。

     

    /** ClassName:SDPClassLoader */ //自己手写的类加载器 public class SDPClassLoader extends ClassLoader { //当前类所对应的绝对路径 private File dir = null; public SDPClassLoader() { this.dir = new File(SDPClassLoader.class.getResource("").getPath()); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { //获取当前包对应当前项目的(src)的相对路径 String className = SDPClassLoader.class.getPackage().getName() + "." + name; //创建文件输入流,读入动态生成的.class文件 FileInputStream fis = null; File classFile = new File(dir+"\\"+name); try { //将字节码从文件读入内存 fis = new FileInputStream(classFile); //将字节码从内存写入JVM虚拟机 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; byte[] car = new byte[1024]; while((len = fis.read(car)) != -1){ bos.write(car,0,len); } //返回生成的Class 类对象 return defineClass(className.replace(".class",""),bos.toByteArray(),0,bos.size()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(fis!=null){ try { //释放资源 fis.close(); } catch (IOException e) { e.printStackTrace(); } } classFile.delete(); } return super.findClass(name); } }

     

     

    最后是我们的测试类。

    public class MyTest { @Test public void fun1() throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //生成被代理的对象 YAAG yaag = new YAAG(); //根据被代理的对象生成代理对象 Person proxyInstance = (Person) SDPProxyUtil.newProxyInstance (new SDPClassLoader(),yaag.getClass().getInterfaces(),new SDPInvocationHandler(yaag)); System.out.println(proxyInstance.say()); System.out.println("________________________________________________________"); proxyInstance.eat(); } }

     

    github源码地址(SSH):git@github.com:xupt-sdp/com.sdp.writeDynamicProxy.git

    github源码地址(Https):https://github.com/xupt-sdp/com.sdp.writeDynamicProxy.git

    最新回复(0)