代理模式(Proxy Pattern)是23种设计模式中的一种,属于结构型设计模式。代理模式给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。
举个例子:你要去吃饭,你可以选择自己在家做饭、吃饭、刷碗,所有的事情都自己做;也可以选择去餐厅,自己只是吃饭,把做饭和刷碗的活儿都交给代理对象,也就是餐厅的工作人员。
下图是代理模式的通用类图。结合例子,就很容易理解了。
代理模式通用类图
代理模式包含如下角色:
代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为JDK动态代理和CGLIB代理两种。
在jdk的动态代理机制中,有几个重要的角色:
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
InvocationHandler这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
这个方法一共接受三个参数,那么这三个参数分别代表如下:
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:
所以我们所说的DynamicProxy(动态代理类)是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。这个DynamicProxy其实就是一个Proxy,它不会做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
被代理对象:
/** * 抽象主题角色 */interface Subject { void eat();}/** * 真实主题角色 - 你自己 - 专注吃饭 */class YourSelf implements Subject{ @Override public void eat() { System.out.println("自己吃饭"); }}
代理对象:
/** * 代理主题角色 - 餐厅 * 每次生成动态代理类对象时都需要指定一个实现了InvocationHandler接口的调用处理器对象 */class JdkProxySubject implements InvocationHandler { // 这个就是我们要代理的真实对象,也就是真正执行业务逻辑的类 private Object target; // 通过构造方法传入这个被代理对象 public JdkProxySubject(Object target) { super(); this.target = target; } // 创建代理对象 public Object createProxy() { // 1.得到目标对象的类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); // 2.得到目标对象的实现接口 Class<?>[] interfaces = target.getClass().getInterfaces(); // 3.第三个参数需要一个实现invocationHandler接口的对象 Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this); return newProxyInstance; } // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 @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; }}
测试类:
/** * 测试类 * @author tianxiaopeng@hxy * @date 2023/10/11 11:09 AM */public class ProxyTest { public static void main(String[] args) { // 1.创建对象 YourSelf yourSelf = new YourSelf(); // 2.创建代理对象 JdkProxySubject proxy = new JdkProxySubject(yourSelf); // 3.调用代理对象的增强方法,得到增强后的对象 Subject createProxy = (Subject) proxy.createProxy(); createProxy.eat(); }}
JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。
<dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2</version></dependency>
net.sf.cglib.proxy.Enhancer:主要增强类,通过字节码技术动态创建委托类的子类实例。
Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。
net.sf.cglib.proxy.MethodInterceptor:常用的方法拦截器接口,需要实现intercept方法,实现具体拦截处理。
public java.lang.Object intercept(java.lang.Object obj, java.lang.reflect.Method method, java.lang.Object[] args, MethodProxy proxy) throws java.lang.Throwable{}
创建被代理类。
/** * 真实主题角色 - 你自己 - 专注吃饭 */class YourSelf { public void eat(){ System.out.println("自己吃饭"); }}
创建代理类:
/** * 代理主题角色 - 餐厅 */class ProxyCglib implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置需要创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通过字节码技术动态创建子类实例 return enhancer.create(); } //实现MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("餐厅工作人员做饭......"); //通过代理类调用父类中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("餐厅工作人员刷碗......"); return result; }}
测试类:
/** * 测试类 * @author tianxiaopeng@hxy * @date 2023/10/11 11:51 AM */public class CglibTest { public static void main(String[] args) { ProxyCglib proxy = new ProxyCglib(); //通过生成子类的方式创建代理类 YourSelf proxyImp = (YourSelf)proxy.getProxy(YourSelf.class); proxyImp.eat(); }}
结果:
餐厅工作人员做饭......自己吃饭餐厅工作人员刷碗......
CGLib动态代理采用了FastClass机制,其分别为代理类和被代理类各生成一个FastClass,这个FastClass类会为代理类或被代理类的方法分配一个 index(int类型)。这个index当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用更高。
但是我们看上面的源码也可以明显看到,JDK动态代理只生成一个文件,而CGLIB生成了三个文件,所以生成代理对象的过程会更复杂。
本文链接:http://www.28at.com/showinfo-26-38516-0.html详解JDK动态代理和CGLib动态代理
声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。