跳转至

代理模式

介绍

代理模式的三大角色:

  1. 目标对象
  2. 代理对象
  3. 目标对和代理对象的公共接口(相同的行为)

如果使用代理模式,客户端程序是无法察觉的。客户端使用代理对象时就像在使用目标对象

可以使用代理模式的场景:

  • 程序中的两个对象无法直接交互
  • 程序中目标对象需要被保护
  • 对象的功能需要被增强

静态代理

场景:需要统计所有业务接口中每个业务方法的耗时情况

  • 不能违背 OCP ,不能违背开闭原则

一种不违背 OCP 的方案:使用继承。分别写一个类去继承各个业务接口的实现类,重写每个业务方法,加上计时逻辑。这种方案使用继承,耦合度过高。

静态代理实现:

  • 目标类和代理类都实现公共接口(此处是某个业务接口)。
  • 将目标对象作为代理类的一个属性。这是一种关联关系,相比继承关系有更低的耦合度。
  • 在代理类的方法中通过目标对象调用目标对象的方法,并且可以据此来进行增强。

缺点:类太多,不好维护。解决方案:动态代理

动态代理

使用字节码生成技术,在程序运行的过程中,在内存中动态地生成字节码形式的代理类

  • JDK 动态代理:只能代理接口
  • CGLIB 动态代理:开源项目。可以代理接口或代理类。底层通过继承的方式实现。在内存中生成一个目标类的子类

JDK 动态代理

OrderService target = new OrderServiceImpl();

// 在内存中创建一个代理对象
OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), TimerInvocationHandler(target));

proxyObj.generateOrder();

newProxyInstance()方法的三个重要的参数:

  1. 第一个参数:ClassLoader loader

    JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

  2. 第二个参数:Class<?>[] interfaces

    代理类和目标类要实现同一个接口或同一些接口。在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

  3. 第三个参数:InvocationHandler

    调用处理器。是一个接口。在调用处理器接口的实现类中编写增强代码

注意:代理对象和目标对象实现的接口一样,所以可以向下转型为接口类型。

invoke() 方法由 JDK 自动调用,其参数如下:

  1. Object proxy
  2. Method method:在 invoke 方法执行的过程中调用的目标对象上的目标方法(要执行的目标方法)
  3. Object[] args:目标方法上的实参

invoke() 方法当代理对象调用代理方法的时候,注册在 InvocationHandler 中的 invoke 方法被调用

public class TimerInvocationHandler implements InvocationHandler {

    // 目标对象
    private Object target;

    public TimerInvocationHandler(Object target) {
        this.target = target;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        long begin = System.currentTimeMillis();

        Object retValue = method.invoke(target, args);

        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        // 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }
}