Java设计模式之代理模式 - JDK 动态代理分析

代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

直白的说就是使用一个代理类,添加对原对象的引用,和原对象有着相同的方法,但是代理类可以在调用原对象的方法的前后添加一些自己的东西,甚至完全替代,但是在外界看来这个动作却像原对象去做的一样。

静态代理

以买房为例,原对象就是真实买家,代理类就是房产中介,买家的动作需要提供资金和相关资料,中介在此基础上增加了房源信息,决策咨询等其他动作,但是在最终的交易中,所有的这些对于卖家来说,都是和买家完成的交易,看起来都是买家所做的动作。

UML图如下

image-20210123210530171

定义一个接口

public interface Buyer {
    /**
     * 购买
     */
    void buy();
}

真实的买家

public class HouseBuyer implements Buyer{

    @Override
    public void buy() {
        System.out.println("要买房");
    }
}

代理中介

public class HouseBuyerProxy implements Buyer{

    private final HouseBuyer houseBuyer;

    /**
     * 代理对象只能引用HouseBuyer
     * @param houseBuyer
     */
    public HouseBuyerProxy(HouseBuyer houseBuyer) {
        this.houseBuyer = houseBuyer;
    }

    @Override
    public void buy() {
        System.out.println("买房前需要验证是否符合购房资格,准备相关材料,办理手续");
        houseBuyer.buy();
    }
}

这样一来,代理类就对真实对象的动作进行了加工改造,也就是代理。

需要实际调用的时候,只需把真实对象传递给代理对象就完成了一次代理

HouseBuyer houseBuyer = new HouseBuyer();
Buyer buyer = new HouseBuyerProxy(houseBuyer);
buyer.buy();

一般来说把上面的这种叫做静态代理,它只能在编码阶段写死一种代理对象,让代理对象持有原始对象的引用,没有办法在运行时去创建代理。

因为代理类和原始类都实现了相同的接口,当原始对象需要添加新的功能是,在接口上新增新的方法时,代理类也要随之修改,这样一来就违反了开闭原则。

当业务模式需要的代理类不是很多的时候可以这么用,实现也比较简单。

有静态代理,那么肯定有动态代理。

动态代理

所谓动态代理,那必然是要能动态改变代理对象,而不是像静态代理那样只能持有一个固定的原始对象来代理。继续以买房为例,这次把代理人更换为房产中介公司,有很多买家的信息,可以给不同的买家进行代理。来看下JDK的动态代理

public class HouseCompanyProxy implements InvocationHandler {

    private Buyer target;

    public Object getInstance(Buyer buyer) {
        this.target = buyer;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object object = method.invoke(this.target, args);
        after();
        return object;
    }

    private void before() {
        System.out.println("before method ...");
    }

    private void after() {
        System.out.println("after method ...");
    }
}

JDK动态代理的代理类必须实现InvocationHandler接口,可以看到这个接口是java.lang.reflect包里的,那么很明显这个动态代理是和反射相关的。

相比静态代理持有对原始对象的引用,动态代理引用的是接口,这样在实际调用的时候就能代理只要实现了这个接口的所有类,而不是静态代理的指定具体类。

同样是对HouseBuyer做代理,动态代理是这样的

 Buyer buyer = (Buyer) new HouseCompanyProxy().getInstance(new HouseBuyer());
 buyer.buy();

通过断点调试看到实际上是生成了一个名为$Proxy0的代理对象

image-20210126114856494

来捕获这个生成的代理对象看看到底干了什么,在上面调用代理的代码后面加上下面的代码

try {
    byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Buyer.class});
    FileOutputStream outputStream = new FileOutputStream("$Proxy0.class");
    outputStream.write(bytes);
} catch (Exception e) {
    e.printStackTrace();
}

$Proxy0.class文件用IDEA打开,省略部分代码

public final class $Proxy0 extends Proxy implements Buyer {
    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 void buy() throws  {
        try {
            super.h.invoke(this, m3, (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("xxx.xxx.xxx.Buyer").getMethod("buy");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

首先通过反射加载了Buyer接口的所有方法,然后通过父类的h变量来执行invoke方法,看着是不是有点熟悉,继续往里看,这个h是继承自Proxy的,进去看看有这么一段,

/**
 * 代理示例的invocation handler
 */
protected InvocationHandler h;

/**
 * 私有化无参数构造方法,禁止实例化
 */
private Proxy() {
}

/**
 * 为指定值的invocation handler从子类构建一个实例(通常是动态代理类)
 */
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

先记住有这么一段带参数的构造方法,然后我们再来从代理开始来走一遍

(Buyer) new HouseCompanyProxy().getInstance(new HouseBuyer());

getInstance方法里Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);

image-20210126151740846

这里代理类作为参数进入这个方法

image-20210126152658298

继续往下走

image-20210126153514051

这里很关键,cons.newInstance(new Object[]{h})这里$Proxy0开始实例化了,回头再去看下上面生成文件里的代码

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

并且这个类继承自Proxy类,实例化的时候执行父类的构造方法,所以又回到了Proxy类。

image-20210126154225159

至此,代理对象的实例化完成,Proxy类里的InvocationHandler成功获取到了代理类的实例

继续往下走,生成的代理对象要执行buy方法,观察$Proxy0里的buy方法

super.h.invoke(this, m3, (Object[])null);

实际执行的是父类InvocationHandlerinvoke方法,而此时InvocationHandler 持有的是子类HouseCompanyProxy的引用,所以实际执行的是HouseCompanyProxyinvoke方法,也就是我们自己实现的那部分,并且还可以自由发挥添加其他的动作。

梳理一下JDK动态代理的实现原理

  1. 拿到被代理类的引用,并通过反射获取它的所有接口
  2. JDK Proxy 类重新生成一个新的类,实现了被代理类的所有接口的方法
  3. 通过新生成的类去执行被代理类的方法,通过多态间接的调用代理类的方法,实现方法的增强

小结

通过代理模式的使用,在客户端和目标对象之间形成一个中介的作用,可以对更好的保护目标对象、增强目标对象。

静态代理功能比较单一,只能对单一对象进行代理。

JDK的动态代理通过传入不同的参数生成一个新的代理对象来目标对象的方法进行操作,如果需要更换目标对象,只需要更换一个参数给到代理类实例化即可。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值