百度网盘怎样做网站,邯郸有建网站的吗,目前做网站最好的语言是,介绍做ppt高大上图表的网站手写实现一个动态代理框架 什么是代理模式什么是动态代理动态代理中的编译、类加载与对象实例化手写实现一个动态代理框架实现细节DynamicProxyHandlerProxy生成代码写入代码到磁盘文件调用编译器进行编译调用类加载器进行类加载反射实例化删除前面生成的java文件和class文件 C… 手写实现一个动态代理框架 什么是代理模式什么是动态代理动态代理中的编译、类加载与对象实例化手写实现一个动态代理框架实现细节DynamicProxyHandlerProxy生成代码写入代码到磁盘文件调用编译器进行编译调用类加载器进行类加载反射实例化删除前面生成的java文件和class文件 Coder 来玩一把 JDK动态代理和我们的区别 最近写了一个动态代理框架在这里分享一下顺便分析一下动态代理的原理当做复习。 什么是代理模式
首先我们来了解一下代理模式代理模式是二十三种设计模式中的其中一种用于对目标对象进行代理增强。 代理类通常和目标类实现同一个接口这样我们就可以用一个接口类型变量去引用一个代理类对象然后又可以当成目标对象去使用。 public interface Handler {void handle();
}
public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target target;}...
}
public class Target implements Handler {...
}
public class Main {public static void main(String[] args) {Target target new Target();Handler handler new Proxy(target);...}
}当我们对目标对象做了代理之后就可以在调用目标对象方法的前后添加一些增强的处理逻辑。 public class Proxy implements Handler {private Handler target;public Proxy(Handler target) {this.target target;}Overridepublic void handle() {pre(); // 前置增强逻辑target.handle();post(); // 后置增强逻辑}
}public class Target implements Handler {Overridepublic void handle() {...}
} 比如我们要在目标方法的前后添加日志打印那么我们可以通过代理模式实现。
public class Proxy implements Handler {private Handler target;Overridepublic void handle() {log.info(...);target.handle();log info(...);}}这样做的好处有两点
我们可以为目标类添加额外的处理逻辑而不需要改动目标类原有的代码。我们可以更换目标对象但是代理类中的增强处理逻辑不用变更换后的目标对象依然可以复用原先的增强逻辑。
什么是动态代理
动态代理是对普通代理模式的优化普通代理模式有一个缺点那就是我们需要手动编写代理逻辑。
比如我们的代理逻辑就是在目标方法的前后添加日志打印。如果只有几个接口需要代理那么我们可以手动编写这几个接口的代理实现类那是没有问题的。
public interface HandlerA {void method1();void method2();void method3();}
public interface HandlerB {void method1();void method2();
}
public interface HandlerC {void method1();void method2();void method3();void method4();
}public class ProxyA implements HandlerA {private HandlerA target;Overridepublic void method1() {log.info(...);target.method1();log info(...);}Overridepublic void method2() {log.info(...);target.method2();log info(...);}Overridepublic void method3() {log.info(...);target.method3();log info(...);}}public class ProxyB implements HandlerB {private HandlerB target;Overridepublic void method1() {log.info(...);target.method1();log info(...);}Overridepublic void method2() {log.info(...);target.method2();log info(...);}}
public class ProxyC implements HandlerC {...
}但是我们发现虽然我们编写的代理类实现了不同的接口重写了不同的接口方法但是前后添加的增强逻辑其实是一模一样的。而且如果接口比较多的话我们一个个手写代理实现类也挺麻烦的。 于是就有了动态代理动态代理的主要作用就是动态生成代理实现类不需要我们手动编写。我们只需要定义好增强逻辑以及需要代理的接口和接口方法我们就可以通过动态代理框架生成代理类并实例化代理对象。 比如JDK的动态代理我们定义好我们的接口比如Handler然后实现JDK动态代理提供的接口InvocationHandler在里面编写增强逻辑就可以作为输入参数调用JDK动态代理提供的工具类Proxy生成代理实现类并反射实例化代理对象。 这样就不需要我们自己编写那么多的代理实现类了省掉了一大波繁琐的工作一片岁月静好。 动态代理中的编译、类加载与对象实例化
我们自己手动编写代理类时从代理类代码的编写到代理对象的创建整个流程是这样子 代码编写和编译器编译是静态的因为是在JVM启动之前提前做好的。
动态代理相当于是就是在JVM运行的时候由动态代理框架根据我们给定的接口和代理逻辑动态编写代理实现类然后调用编译器进行动态编译通过类加载器加载到内存中然后反射实例化。 在动态代理的情况下代码是在JVM运行时动态生成的比如用StringBuilder拼接也可以是别的方式生成然后通过调用JDK提供的API进行运行时编译类加载器加载也是通过调用JDK提供的API进行动态的加载。也就是说从代码生成、编译、类加载、到反射实例化的这一切都是在JVM已经运行的情况下通过Java代码动态实现的因此称为动态代理。
手写实现一个动态代理框架
那么我们就来实现一个我们自己的动态代理框架非常简单三个类Coder、DynamicProxyHandler、Proxy
Coder代理类代码的生成器类接收一个Class?类型的参数interfaceClass表示代理类要实现interfaceClass这个接口Coder根据这个接口生成代理类的代码DynamicProxyHandler留给用户实现的处理器接口用户需要实现DynamicProxyHandler接口并重新invoke以声明动态代理的增强逻辑Proxy生成代理对象的工具类调用Coder生成代理类的代码 通过编译器动态编译然后通过类加载器动态加载编译出来的代理类class最后通过反射创建代理对象返回给用户 实现细节
DynamicProxyHandler
DynamicProxyHandler就是预留给用户实现的用于编写增强逻辑的接口用户需要实现DynamicProxyHandler接口并重写invoke方法在invoke方法中编写增加逻辑。
/*** author huangjunyi* date 2023/11/28 19:00* desc*/
public interface DynamicProxyHandler {Object invoke(Object proxy, Method method, Object[] args);}invoke方法接收三个参数proxy是代理对象本身method是当前正在调用的方法的Method对象args是当前方法接收的参数。
Proxy
Proxy中的代码稍稍有点复杂不用马上全部看完先有个大概印象下面一步步分析里面干了些啥。
/*** author huangjunyi* date 2023/11/28 19:00* desc*/
public class Proxy {private static boolean enablePrintCode false;public static void printCode() {enablePrintCode true;}public static T T newInstance(ClassLoader classLoader, Class? interfaceClass, DynamicProxyHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {String code Coder.generateCode(interfaceClass);if (enablePrintCode) {System.out.println(code);}String path classLoader.getResource().getPath() interfaceClass.getPackage().toString().substring(package .length()).replaceAll(\\., /) / interfaceClass.getSimpleName() $$.java;path path.replace(\\, /);File file new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter new FileWriter(file);fileWriter.write(code);fileWriter.close();JavaCompiler compiler ToolProvider.getSystemJavaCompiler();int result compiler.run(null, null, null, path);String parentPath path.substring(0, path.lastIndexOf(/));URL[] urls new URL[] {new URL(file:/ parentPath /)};URLClassLoader loader new URLClassLoader(urls);Class c loader.loadClass(interfaceClass.getPackage().toString().substring(package .length()) . interfaceClass.getSimpleName() $$);Constructor constructor c.getConstructor(DynamicProxyHandler.class);Object o constructor.newInstance(handler);new File(path.substring(0, path.lastIndexOf(.) 1) class).delete();file.delete();return (T) o;}}生成代码
首先调用Coder生成代理类的代码接收一个接口类型参数interfaceClass返回生成的代码字符串。 String code Coder.generateCode(interfaceClass);Proxy里面有个boolean类型的enablePrintCode属性把它设置为true会打印动态生成的代理类的代码。 if (enablePrintCode) {System.out.println(code);}写入代码到磁盘文件
然后把生成的代码写入到磁盘文件 String path classLoader.getResource().getPath() interfaceClass.getPackage().toString().substring(package .length()).replaceAll(\\., /) / interfaceClass.getSimpleName() $$.java;path path.replace(\\, /);File file new File(path);if (file.getParentFile().exists()) {file.getParentFile().mkdir();}FileWriter fileWriter new FileWriter(file);fileWriter.write(code);fileWriter.close();磁盘文件的路径必须在类加载器加载类的目录下通过 classLoader.getResource(“”).getPath() 取得类加载器加载类的对应目录。
然后把类的包名中的“.”替换成“/”创建目录。
最后创建文件通过FileWriter把代码写入到文件中。
调用编译器进行编译
代码写到文件后就要调用编译器把这个java文件编译成class文件。 JavaCompiler compiler ToolProvider.getSystemJavaCompiler();int result compiler.run(null, null, null, path);JavaCompiler就是编译器对象调用JavaCompiler的run方法进行编译path参数是需要编译的java文件的路径。
调用类加载器进行类加载
编译出class文件后就要调用类加载器把这个class文件加载到内存中生成一个Class对象。 String parentPath path.substring(0, path.lastIndexOf(/));URL[] urls new URL[] {new URL(file:/ parentPath /)};URLClassLoader loader new URLClassLoader(urls);Class c loader.loadClass(interfaceClass.getPackage().toString().substring(package .length()) . interfaceClass.getSimpleName() $$);String parentPath path.substring(0, path.lastIndexOf(“/”)); 这一行是取得java文件所在目录的路径编译出来的class文件与java文件是同处一个目录下的这样我们就可以找到class文件。
然后就是创建URLClassLoader对象调用URLClassLoader的loadClass方法进行类加载获得代理类的Class对象。
反射实例化
获得代理类的Class对象后就要反射进行实例化。 Constructor constructor c.getConstructor(DynamicProxyHandler.class);Object o constructor.newInstance(handler);获得构造器对象constructor调用构造器的newInstance方法进行实例化。
删除前面生成的java文件和class文件
最后要把生成的文件删除掉以免留下垃圾。 new File(path.substring(0, path.lastIndexOf(.) 1) class).delete();file.delete();第一行是删除class文件第二行是删除java文件。
Coder
接下来看一下Coder类。这个Coder类不必细看代码虽然多但是都是使用StringBuilder进行字符串拼接根据给定的接口拼出一个代理类没什么技术含量。
/*** author huangjunyi* date 2023/11/28 19:00* desc*/
public class Coder {private static MapString, String codeCache new HashMap();public static String generateCode(Class? interfaceClass) {String interfaceClassName interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}StringBuilder codeBuilder new StringBuilder();// packagePackage aPackage interfaceClass.getPackage();String packageName aPackage.getName();codeBuilder.append(package ).append(packageName).append(;).append(\n);codeBuilder.append(\n);// importMethod[] methods interfaceClass.getDeclaredMethods();SetString importTypeSet new HashSet();for (Method method : methods) {Class? returnType method.getReturnType();importTypeSet.add(returnType.getName());Class?[] parameterTypes method.getParameterTypes();for (Class? parameterType : parameterTypes) {importTypeSet.add(parameterType.getName());}}for (String importType : importTypeSet) {if (void.equals(importType) ||long.equals(importType) ||int.equals(importType) ||short.equals(importType) ||byte.equals(importType) ||char.equals(importType) ||float.equals(importType) ||double.equals(importType) ||boolean.equals(importType)) {continue;}codeBuilder.append(import ).append(importType).append(;).append(\n);}codeBuilder.append(import java.lang.reflect.Method;);codeBuilder.append(import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;);// classcodeBuilder.append(\n\npublic class ).append(interfaceClass.getSimpleName()).append($$).append( implements).append( ).append(interfaceClass.getSimpleName()).append( {).append(\n\n);// fieldcodeBuilder.append(\tprivate DynamicProxyHandler dynamicProxyHandler;\n\n);// ConstructorcodeBuilder.append(\tpublic ) .append(interfaceClass.getSimpleName()).append($$).append(().append(DynamicProxyHandler dynamicProxyHandler) {\n).append(\t\tthis.dynamicProxyHandler dynamicProxyHandler;).append(\n).append(\t}\n\n);// methodfor (Method method : methods) {Class? returnType method.getReturnType();importTypeSet.add(returnType.getName());codeBuilder.append(\tOverride\n);codeBuilder.append(\t).append(public ).append(returnType.getSimpleName()).append( ).append(method.getName()).append(();// method parameterClass?[] parameterTypes method.getParameterTypes();for (int i 0; i parameterTypes.length; i) {Class? parameterType parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append( ).append(var).append(i);if (i parameterTypes.length - 1) {codeBuilder.append(, );}}codeBuilder.append() ).append( {).append(\n);// method bodycodeBuilder.append(\n\t\tMethod method null;);codeBuilder.append(\n\t\ttry {);codeBuilder.append(\n\t\t\tmethod Class.forName().append(\).append(interfaceClass.getName()).append(\).append()).append(.getDeclaredMethod().append(\).append(method.getName()).append(\);if (parameterTypes.length ! 0) {codeBuilder.append(, );for (int i 0; i parameterTypes.length; i) {Class? parameterType parameterTypes[i];codeBuilder.append(parameterType.getSimpleName()).append(.).append(class);if (i parameterTypes.length - 1) {codeBuilder.append(, );}}}codeBuilder.append(););codeBuilder.append(\n\t\t\tmethod.setAccessible(true););codeBuilder.append(\n\t\t} catch (ClassNotFoundException|NoSuchMethodException e) {);codeBuilder.append(\n\t\t\tthrow new RuntimeException(\error when generate code\););codeBuilder.append(\n\t\t});if (!void.equals(returnType.getSimpleName())) {codeBuilder.append(\n\t\treturn ().append(returnType.getSimpleName()).append() ).append(dynamicProxyHandler.invoke(this, method, new Object[]{);} else {codeBuilder.append(dynamicProxyHandler.invoke(this, method, new Object[]{);}for (int i 0; i parameterTypes.length; i) {codeBuilder.append(var).append(i);if (i parameterTypes.length - 1) {codeBuilder.append(, );}}codeBuilder.append(}););codeBuilder.append(\n).append(\t).append(});codeBuilder.append(\n\n);}codeBuilder.append(});String code codeBuilder.toString();codeCache.put(interfaceClassName, code);return code;}}Coder 有一个MapString, String类型的缓存codeCache用于缓存曾经生成过的代码如果下一次再调用Coder的generateCode传入的参数interfaceClass是同一个接口类型那么直接取缓存中的结果返回不再重复生成。 String interfaceClassName interfaceClass.getName();if (codeCache.containsKey(interfaceClassName)) {return codeCache.get(interfaceClassName);}如果缓存中没有就要通过StringBuilder动态拼接代理类的代码了。 StringBuilder codeBuilder new StringBuilder();...这个拼接的逻辑就不用细看了只是繁琐的工作只要知道怎么通过Class对象获取到类信息和方法信息就可以根据这些信息拼出一个代理实现类。
Package aPackage interfaceClass.getPackage(); 获取包路径。Method[] methods interfaceClass.getDeclaredMethods(); 获取接口定义的方法。Class? returnType method.getReturnType(); 获取方法返回值类型。Class?[] parameterTypes method.getParameterTypes(); 获取方法参数类型。
我们看看生成的代理类是怎么样的。比如我们有一个Hello接口
/*** author huangjunyi* date 2023/11/28 19:58* desc*/
public interface Hello {String sayHi(int count);}生成的代理类就是这样
package com.huangjunyi1993.simple.dynamic.proxy.test;import java.lang.String;
import java.lang.reflect.Method;import com.huangjunyi1993.simple.dynamic.proxy.DynamicProxyHandler;public class Hello$$ implements Hello {private DynamicProxyHandler dynamicProxyHandler;public Hello$$(DynamicProxyHandler dynamicProxyHandler) {this.dynamicProxyHandler dynamicProxyHandler;}Overridepublic String sayHi(int var0) {Method method null;try {method Class.forName(com.huangjunyi1993.simple.dynamic.proxy.test.Hello).getDeclaredMethod(sayHi, int.class);method.setAccessible(true);} catch (ClassNotFoundException|NoSuchMethodException e) {throw new RuntimeException(error when generate code);}return (String) dynamicProxyHandler.invoke(this, method, new Object[]{var0});}}生成的代理类的类名是接口名后加两个“$”比如这里的接口是Hello那么生成的代理类就是Hello$$。
public class Hello$$ implements Hello生成的代理类重写了给定接口定义的所有方法方法里面的逻辑都是反射获取当前方法的Method对象然后调用DynamicProxyHandler的invoke方法把当前代理对象this、当前方法的Method对象method、当前方法参数作为invoke方法的入参。
来玩一把
定义一个Hello接口就是前面的Hello接口
/*** author huangjunyi* date 2023/11/28 19:58* desc*/
public interface Hello {String sayHi(int count);}Hello接口实现类HelloImpl目标类被代理类
/*** author huangjunyi* date 2023/11/28 20:00* desc*/
public class HelloImpl implements Hello {Overridepublic String sayHi(int count) {String result ;for (int i 0; i count; i) {result hi ;}return result;}
}实现DynamicProxyHandler接口编写增强逻辑
/*** author huangjunyi* date 2023/11/28 20:02* desc*/
public class HelloHandler implements DynamicProxyHandler {private Hello target;public HelloHandler(Hello target) {this.target target;}Overridepublic Object invoke(Object proxy, Method method, Object[] args) {System.out.println(HelloHandler invoke);try {Object invoke method.invoke(target, args);System.out.println(invoke);return invoke;} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}
}测试类
/*** author huangjunyi* date 2023/11/24 10:05* desc*/
public class A {Testpublic void test() throws Exception {HelloImpl hello new HelloImpl();HelloHandler helloHandler new HelloHandler(hello);// 调用这个方法可以打印生成的代理类的代码// Proxy.printCode();Hello o Proxy.newInstance(hello.getClass().getClassLoader(), Hello.class, helloHandler);o.sayHi(5);}}运行测试类控制台输出
HelloHandler invoke
hi hi hi hi hi JDK动态代理和我们的区别
JDK动态代理大体流程和我们的思路是相同的我们看看JDK的Proxy类的newProxyInstance方法 public static Object newProxyInstance(ClassLoader loader,Class?[] interfaces,InvocationHandler h)throws IllegalArgumentException{final Class?[] intfs interfaces.clone();// 生成代理类的Class对象Class? cl getProxyClass0(loader, intfs);try {// 获取构造器对象final Constructor? cons cl.getConstructor(constructorParams);final InvocationHandler ih h;cons.setAccessible(true);// 构造器反射实例化return cons.newInstance(new Object[]{h});} catch (...) {...}}里面我省略了许多杂七杂八的代码只留下核心流程可以看到也是生成一个代理类的Class对象然后反射调用它的构造器生成代理对象。
getProxyClass0(loader, intfs)最终会调到ProxyClassFactory的apply方法 public Class? apply(ClassLoader loader, Class?[] interfaces) {// 。。。。。。// 生成的不是字符串而是二进制byte数组class字节码文件里面的内容byte[] proxyClassFile ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 调用native方法直接进行类的初始化生成Class对象// 没有写盘、编译、类加载的这几步动作return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {...}}这里就是和我们区别最大的地方JDK动态代理不是通过字符串拼接生成的代理类代码而是生成的byte[]类型里面的内容也不是java文件里面的代码而是class字节码文件里面的内容。
然后直接调用defineClass0方法defineClass0方法是native方法进行类的初始化省略了写入磁盘我们也可以设置要保存生成的class文件这样会把class文件写到磁盘、编译、调用类加载器进行类加载的这几步。 全文完。