设计模式之代理模式从静态代理到动态代理

2023年 7月 14日 44.5k 0

设计模式之代理模式-从静态代理到动态代理

代理模式看似离我们的开发比较远,不像策略模式、工厂模式那么的直观,但是其实在我们日常开发的许多场景都用到了代理模式,比如:Spring AOP使用了JDK动态代理 和Cglib动态代理 ;Mybatis的插件使用了JDK动态代理 ;

代理模式的优点

见名之意,代理模式的核心就是「代理对象」,这个代理对象相当于一个中介,而我们被代理的对象叫做「目标对象」,你可以把它想象成委托人。中介起到的作用就是隔离了委托人和客户 (说不定委托人不想和客户见面-安全性),让他们免去了繁琐的流程 (比如租房吧,租客和房东不需要见面,他们只需要和房屋中介进行对接就行),这在代码中呢,就叫做解除了「调用方」和「目标对象」的的耦合度。

综上,代理模式的优点如下:

  • 通过代理对象将真正的目标对象隔离起来,起到了一定的保护作用
  • 解除了调用方和被调用方的直接联系,一定程度上降低了系统的耦合度
  • 代理对象可以做目标对象提供功能外的一些事情,达到对目标功能的增强作用

在开发中,最常遇见的三种代理模式:静态代理、JDK动态代理、CGLIB动态代理。

静态代理

静态代理的角色:1、功能接口;2、被代理类;3、代理类

还是拿租房的例子来写对应的代码:

功能接口-把房子租出去

public interface RentOutHouse {  
    void rentOut();  
}

被代理方-委托方,房东

public class Landlord implements RentOutHouse{  
@Override  
public void rentOut() {  
    // 核心功能-租房子出去  
    System.out.println("我是房东,我有一个房子需要租出去");  
    }  
}

代理对象-中介

public class HouseProxy implements RentOutHouse{  
// 中介要和房东直接接触  
private Landlord landlord;  
  
public HouseProxy(Landlord landlord) {  
    this.landlord = landlord;  
}  
  
@Override  
public void rentOut() {  
    // 核心功能前的功能增强
    System.out.println("我是中介,租房之前和房东签合同");  
    System.out.println("我是中介,租房之前和租房者签合同");  
    // 核心功能
    landlord.rentOut();  
    // 核心功能后的增强
    System.out.println("租出去了之后,我要多收取一些中介费");  
}

客户端-租客

public class Client {  
    public static void process() {  
        HouseProxy houseProxy = new HouseProxy(new Landlord());  
        houseProxy.rentOut();  
    }  

    public static void main(String[] args) {  
        process();  
    }  
}

结果

我是中介,租房之前和房东签合同
我是中介,租房之前和租房者签合同
我是房东,我有一个房子需要租出去
租出去了之后,我要多收取一些中介费

静态代理的缺点:

1、每个被代理类都需要一个代理类与之对应,因为代理类中是要包含这个被代理类对象的,所以假如再来一个房东需要将自己的房子租出去,还需要创建一个新的中介去和这个新房东对接,而其实中介对于两个房东要做的增强事情是一样的,所以会造成冗余。

2、
再有,如果我房东现在是把房子租出去了,如果我想取消出租呢,这时候就需要顶层的那个接口再加一个取消出租的抽象方法,这时候就芭比Q了,因为你所有的房东和房屋中介都需要去重写这个方法,hhh。

JDK动态代理

JDK动态代理运用了反射的技术,很好的解决了上述静态代理的不足,先看看代码和执行的结果:

功能接口-把房子租出去

public interface RentOutHouse {  
    void rentOut();  
}

被代理方-委托方,房东

public class Landlord implements RentOutHouse{  
@Override  
public void rentOut() {  
    // 核心功能-租房子出去  
    System.out.println("我是房东,我有一个房子需要租出去");  
    }  
}

方法拦截器-InvocationHandler

public class MyInvocationHandler implements InvocationHandler {  
    private Object object;  

    public MyInvocationHandler(Object object) {  
        this.object = object;  
    }  

    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        // 核心功能前的功能增强  
        System.out.println("我是中介,租房之前和房东签合同");  
        System.out.println("我是中介,租房之前和租房者签合同");  
        // 核心功能方法调用  
        Object invoke = method.invoke(object, args);  
        // 核心功能后的增强  
        System.out.println("租出去了之后,我要多收取一些中介费");  
        return invoke;  
    }  
}

客户端-租客

public class Client {  
  
    public static void main(String[] args) {  
        RentOutHouse landlord = new Landlord();  
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(landlord);  

        RentOutHouse proxy = (RentOutHouse)Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);  
        proxy.rentOut();  
    }  
}

注意,这里的Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);是用来生成代理对象的,三个参数分别是:1、类加载器;2、类的全部接口;3、自定义的方法拦截器;

InvocationHandler是方法拦截器,它会将被代理类的方法进行拦截,并加上自己的一些增强,核心就是它里面的invoke()方法。

我这里写的InvocationHandler比较简单,其实可以在它里面的invoke()方法中做一些复杂的判断,比如 "rentout".equals(method.getName())来指定只有方法名为rentout时我才去进行增强。

这时你会说,不是说用到了反射么,在哪里?别急,先看看我们实际生成的代理对象到底是个什么东东:

在租客类中打个断点:

看代理类的类型:@Proxy0@513,这个类你是找不到的,我们来把它反编译出来,你就会知道反射用在哪里了~~~

/**  
* 生成字节码  
*/  
private static void saveProxyClass(String path) {  
   byte[] $proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", Landlord.class.getInterfaces());  
   FileOutputStream fileOutputStream = null;  
   try {  
       fileOutputStream = new FileOutputStream(new File(path + "$Proxy0.class"));  
       fileOutputStream.write($proxy0s);  
   } catch (Exception e) {  
       throw new RuntimeException(e);  
   }finally {  
       try {  
           fileOutputStream.flush();  
           fileOutputStream.close();  
       } catch (IOException e) {  
           throw new RuntimeException(e);  
       }  
   }  
}

看看生成的class文件:

public final class $Proxy0 extends Proxy implements RentOutHouse {  
   private static Method m1;  
   private static Method m2;  
   private static Method m3;  
   private static Method m0;  

   public $Proxy0(InvocationHandler var1) throws {  
       super(var1);  
   }  

   public final boolean equals(Object var1) throws {  
       try {  
           return (Boolean)super.h.invoke(this, m1, new Object[]{var1});  
       } catch (RuntimeException | Error var3) {  
           throw var3;  
       } catch (Throwable var4) {  
           throw new UndeclaredThrowableException(var4);  
       }  
   }  

   public final String toString() throws {  
       try {  
           return (String)super.h.invoke(this, m2, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  

   public final void rentOut() throws {  
       try {  
           super.h.invoke(this, m3, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  

   public final int hashCode() throws {  
       try {  
           return (Integer)super.h.invoke(this, m0, (Object[])null);  
       } catch (RuntimeException | Error var2) {  
           throw var2;  
       } catch (Throwable var3) {  
           throw new UndeclaredThrowableException(var3);  
       }  
   }  

   static {  
       try {  
           m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));  
           m2 = Class.forName("java.lang.Object").getMethod("toString");  
           m3 = Class.forName("com.hss.spring.jdkdynamic.RentOutHouse").getMethod("rentOut");  
           m0 = Class.forName("java.lang.Object").getMethod("hashCode");  
       } catch (NoSuchMethodException var2) {  
           throw new NoSuchMethodError(var2.getMessage());  
       } catch (ClassNotFoundException var3) {  
           throw new NoClassDefFoundError(var3.getMessage());  
       }  
   }  
}

像equals、hashCode那几个方法不看,我们就看我们代理的方法rentOut(),你会看到,当我们调用代理对象的rentOut()方法的时候,它内部是走了super.h.invoke(this, m3, (Object[])null);,这里,super是指父类,很容易看到是Proxy类,super.h又是什么?跟进去看看:

可以看到,h其实就是我们在Client里面通过 Proxy.newProxyInstance()生成代理对象时传入的自定义的 InvocationHandler !!!

知道了h是什么,再来看看这里的m3是什么:

哈哈,这里就是通过反射拿到我们接口的方法rentOut(),这样就把这个代理类对象搞懂了,最后一步,就是走到了InvocationHandlerinvoke()方法中:

这就是JDK动态代理的全部流程了,需要注意的是:JDK动态代理要求被代理的类必须实现一个接口。

那么问题来了:为什么JDK动态代理要求被代理的类要实现接口?

答案:首先,要生成代理对象,是必须去知道被代理的类有哪些方法的,可以让被代理类实现接口,或者代理类去继承被代理类,这样代理类就可以得到被代理类中的所有方法。而JDK动态代理之所以需要被代理类实现接口,我们可以从反编译的class文件中看到,代理类对象已经继承了Proxy类,java它又是单继承的,所以说就要求被代理类去实现接口,让我们能够成功的生成代理类。

CGLIB动态代理

CGLIB动态代理和JDK动态代理的本质区别就是:CGLIB不需要被代理类去实现接口(实不实现接口都可以)。

先引入cglib依赖

  
    cglib  
    cglib  
    3.2.7  

被代理方-委托方,房东

public class Landlord {  
    @Override  
    public void rentOut() {  
        // 核心功能-租房子出去  
        System.out.println("我是房东,我有一个房子需要租出去");  
    }  
}

回调-Interceptor

public class MyInterceptor implements MethodInterceptor {  
  
    @Override  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  

        // 核心功能前的功能增强  
        System.out.println("我是中介,租房之前和房东签合同");  
        System.out.println("我是中介,租房之前和租房者签合同");  
        Object obj = methodProxy.invokeSuper(o, objects);  
        System.out.println("租出去了之后,我要多收取一些中介费");  
        return obj;  
    }  
}

客户端-租客

public class Client {  
  
    public static void main(String[] args) {  
        Landlord landlord = new Landlord();  
        // 代理类class文件存入本地磁盘方便我们反编译查看源码  
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./p");  
        // 通过CGLIB获取代理对象  
        Enhancer enhancer = new Enhancer();  
        // 设置代理对象的父类  
        enhancer.setSuperclass(Landlord.class);  
        // 设置回调  
        enhancer.setCallback(new MyInterceptor());  
        Landlord landlordProxy = (Landlord) enhancer.create();  
        // 通过代理对象调用目标方法  
        landlordProxy.rentOut();  
    }  
}

可以看到整体的代码和JDK动态代理非常的类似,区别就是被代理类不做实现接口的要求,Interceptor替换了InvocationHandler,然后在Client中有部分的代码不一样。

核心方法和增强方法的逻辑都是写在Interceptor中的。

这时候你会问,不是说基于继承么,怎么体现的?

看看我们生成的class文件(太多了,只看核心的部分):


内部调用了Interceptor的intercept()方法。

over!!!

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论