青岛做外贸网站哪家好,纯flash网站下载,网站管理建设工作,如何销售做网站文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义
静态代理和动态代理都反映了一个代理模式#xff0c;代理模式是一种经典的设计模式#xff0c;常用于为其他对象提供一种… 文章目录 一、定义二、静态代理三、动态代理1. JDK代理1.1 JDK代理实现流程1.2 动态生成的类字节码 2. Cglib代理2.1 Cglib实现流程 四、总结 一、定义
静态代理和动态代理都反映了一个代理模式代理模式是一种经典的设计模式常用于为其他对象提供一种代理或占位符以限制对它的访问充当访问对象的中介。 就好比平时我们租房会和中介进行沟通中介带我们去看房中介又与房东保持联系但房东不会带我们看房。 二、静态代理
静态代理是一种代理模式的实现静态代理在编译时就确定了代理类和目标类之间的关系。 在静态代理中代理类和目标类通常实现相同的接口或者代理类继承目标类。 代理类作为目标类的包装持有目标类的引用并且调用目标对象的方法前后进行增强操作。 下面举个例子
public interface Landlord {/*** 出租房间*/void rentingHouse();}public class ShenZhenLandlord implements Landlord {Overridepublic void rentingHouse() {System.out.println(深圳房东出租房间);}
}public class Intermediary implements Landlord{/*** 代理类*/private final Landlord landlord;public Intermediary(Landlord landlord) {this.landlord landlord;}Overridepublic void rentingHouse() {System.out.println(中介带租客看房);landlord.rentingHouse();System.out.println(中介收取出租非);}
}public static void main(String[] args) {// 目标类Landlord landlord new ShenZhenLandlord();//代理类Intermediary intermediary new Intermediary(landlord);// 运行代理方法intermediary.rentingHouse();
}这是一个经典的代理模式的实现可以在不修改原对象的情况下对目标类进行增强处理但是若接口新增方法所有代理类都都需要新增对应的实现不好维护。 三、动态代理
动态代理是一个在运行期间动态创建代理对象的技术它允许开发者为一个或多个接口创建一个代理对象且无需事先知道具体实现类。 静态代理和动态代理的区别在于class文件是编译期间确定还是运行期间确定 静态代理在编写代码的时候就明确了代理类和目标类之间的关系在编译阶段会生成一个Class对象。 动态代理则是在运行阶段动态生成代理对象在运行的时候动态生成字节码并加载到JVM中去 1. JDK代理
JDK代理技术是Java提供的一种动态代理技术使用方式如下
public class ProxyFactory {/*** 目标类*/private Object target;public ProxyFactory(Object target) {this.target target;}public Object getProxyInstance() {return Proxy.newProxyInstance(// 目标类加载器target.getClass().getClassLoader(),// 目标对象的接口类型target.getClass().getInterfaces(),// 事件处理new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(前置增强);method.invoke(target, args);System.out.println(后置增强);return null;}});}
}public static void main(String[] args) {ShenZhenLandlord landlord new ShenZhenLandlord();System.out.println(landlord.getClass());Landlord proxyInstance (Landlord) new ProxyFactory(landlord).getProxyInstance();proxyInstance.rentingHouse();System.out.println(proxyInstance.getClass());
}代理类打印出的Class为com.sun.proxy.$Proxy0, 从ShenZhenLandlord到 com.sun.proxy.$Proxy0 其中经历了什么呢 1.1 JDK代理实现流程
需要知道的是JVM虚拟机的类加载过程分为加载、验证、准备、解析、初始化这五个阶段 在加载阶段需要进行下面这几个步骤
通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态结构转化为方法区的运行时数据结构内存生成一个代表这类的Class对象作为方法区这个类的各种数据访问入口
而获取类的二进制字节流JVM提供了三种途径
本地获取字节码比如前面的ShenZhenLandlord类编译后就属于本地的字节码从网络中获取可以使用URLClassLoader来加载类运行时计算生成在程序运行的过程中动态生成类字节码
而代理就是运行时动态生成字节码然后交给JVM进行类加载过程使用。 所以经过JDK代理后从ShenZhenLandlord变成了com.sun.proxy.$Proxy0也就是程序在运行中通过计算生成了$Proxy0代理类的类字节码。 1.2 动态生成的类字节码
由于动态生成的类字节码是动态计算出来并加载到JVM内存中去因此无法通过查看编译后的文件去查看这个代理类的 Class文件。 不过可以通过arthas工具来查看动态生成的类字节码。 下载官网https://arthas.aliyun.com/doc/download.html#%E7%94%A8-arthas-boot-%E5%90%AF%E5%8A%A8 输入命令java -jar arthas-boot.jar 运行arthas proxy.jdk.Main 就是我们要查看的目标类按3进入监控界面如下图所示就代表进入成功了 输入命令jad com.sun.proxy.$Proxy0命令就能解析出$Proxy0的源码了 整理一下代码如下
public final class $Proxy0
extends Proxy
implements Landlord {private static Method m1;private static Method m3;private static Method m2;private static Method m0;// 构造参数为InvocationHandlerpublic $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 Class.forName(proxy.static_proxy.Landlord).getMethod(rentingHouse, new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}// Landlord接口的rentingHouse方法public final void rentingHouse() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}看一个类首先先看构造方法KaTeX parse error: Expected EOF, got # at position 70: …就是ProxyFactory#̲getProxyInstanc…Proxy0的构造方法传进去的也就是是同一个 public Object getProxyInstance() {return Proxy.newProxyInstance(// 目标类加载器target.getClass().getClassLoader(),// 目标对象的接口类型target.getClass().getInterfaces(),// 事件处理new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(前置增强);method.invoke(target, args);System.out.println(后置增强);return null;}});}我们稍微看一下Proxy的源码 private static final Class?[] constructorParams { InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader,Class?[] interfaces,InvocationHandler h)
throws IllegalArgumentException
{final Class?[] intfs interfaces.clone();/** 获取动态生成的字节码*/Class? cl getProxyClass0(loader, intfs);/** 获取这个代理类的构造参数为InvocationHandler的构造方法*/final Constructor? cons cl.getConstructor(constructorParams);final InvocationHandler ih h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedActionVoid() {public Void run() {cons.setAccessible(true);return null;}});}// 将h作为构造参数创建代理类return cons.newInstance(new Object[]{h});
}那么在$Proxy0#rentingHouse方法调用了构造参数传进来的InvocationHandler的invoke方法也就是调用了下面这个方法增强后的方法
new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(前置增强);method.invoke(target, args);System.out.println(后置增强);return null;}
}通过这就可以得出下面的结论
代理类实际上就是实现了Landlord接口重写了rentingHouse方法当外界调用代理类的rentingHouse方法的时候实际上就是调用了new InvocationHandler的invoke方法 2. Cglib代理
CglibCode Generation Library是一个强大的高性能代码生成库用于在运行时扩展Java类和实现接口。它通过字节码技术动态生成新的类从而实现对目标类的代理。Cglib代理是AOP面向切面编程中常用的一种代理方式尤其是在需要代理那些没有实现接口的类时。 举个例子 目标类如下
public class UserServiceImpl {public String selectList(){System.out.println(正在查询);return 小明, 小红, 小黄;}
}public class UserLogProxy implements MethodInterceptor {/*** 生成 CGLIB 动态代理类方法** param target* return*/public Object getLogProxy(Object target) {// 增强器类用来创建动态代理类Enhancer enhancer new Enhancer();// 设置代理类的父类字节码对象enhancer.setSuperclass(target.getClass());// 设置回调enhancer.setCallback(this);// 创建动态代理对象并返回return enhancer.create();}Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println(开始查询);// 执行原始方法Object result methodProxy.invokeSuper(o, objects);System.out.println(查询完毕);return result;}
}public class MethodProxy{public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}}
}public class Main {public static void main(String[] args) {// 目标对象UserServiceImpl userService new UserServiceImpl();System.out.println(userService.getClass());// 代理对象UserServiceImpl proxy (UserServiceImpl) new UserLogProxy().getLogProxy(userService);System.out.println(proxy.getClass());String userList proxy.selectList();System.out.println(userList);while (true) {}}
}2.1 Cglib实现流程
使用arthas工具将proxy.cglib.UserServiceImpl$$EnhancerByCGLIB$$b96d19a3类的代码获取出来。
public class UserServiceImpl$$EnhancerByCGLIB$$b96d19a3
extends UserServiceImpl
implements Factory {private MethodInterceptor CGLIB$CALLBACK_0;public final String selectList() {// 是否设置了回调MethodInterceptor methodInterceptor this.CGLIB$CALLBACK_0;if (methodInterceptor null) {UserServiceImpl$$EnhancerByCGLIB$$b96d19a3.CGLIB$BIND_CALLBACKS(this);methodInterceptor this.CGLIB$CALLBACK_0;}// 设置回调需要调用 intercept 方法if (methodInterceptor ! null) {return (String)methodInterceptor.intercept(this, CGLIB$selectList$0$Method, CGLIB$emptyArgs, CGLIB$selectList$0$Proxy);}// 无回调调用父类的 findUserList 即可return super.selectList();}}在JVM编译阶段Enhancer会根据目标类信息去动态生成代理类并设置回调。 当用户通过Cglib动态代理执行selectList方法的时候会直接调用methodInterceptor.intercept方法在intercept方法中通过invikeSuper调用父类的selectList方法。 如果没有设置回调则直接调用父类的selectList方法。 四、总结 JDK代理与Cglib代理对比 Cglib实现的动态代理是基于ASM字节码生成框架实现的使用字节码技术生成代理类在JDK1.6之前比使用Java反射效率要高一点。但是需要注意Cglib代理技术不可对final类进行类或方法的代理因为Cglib的原理是动态生成代理类的子类实现的。在JDK1.6以后的版本已经开始对JDK代理进行优化具体优化了什么 以后有机会再写篇文章讲在调用次数较少的情况JDK代理的效率要比Cglib代理效率要高只有进行大量调用的时候Cglib的优势才会出现。而到了JDK1.8JDK代理的效率已经高于Cglib代理因此在有接口的情况下推荐优先使用JDK代理没接口优先推荐使用Cglib代理技术。 动态代理与静态代理的比较 动态代理的最大优势就是在于可以把接口中声明的所有方法转移到调用处理器的一个集中的方法进行处理(Invocation.invoke)在接口数量比较多的情况下可以进行灵活处理。如果接口增加一个方法静态代理模式除了所有实现类需要实现这个方法外所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题