「设计模式」- 聊一聊代理模式

270 阅读5分钟

这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战

1. 代理模式

什么是代理模?

举个例子,你对外提供服务,但是你又不想什么阿猫阿狗都来烦你,于是你找了个人作为你的代理人,用来代替他人对你的直接访问,这就是代理模式。

简单来说,就是使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

而代理模式又分为两种:

  1. 静态代理模式:静态代理中,我们对目标对象的每个方法的增强都是手动完成的
  2. 动态代理模式:相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB 动态代理机制

2. 静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活。实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景,他的实现步骤是这样的:

  1. 定义一个接口以及其实现类
  2. 创建一个代理类,同样实现这个接口
  3. 将被代理的类注入这个代理类中,然后在代理类中调用具体的方法

这样做虽然屏蔽了被代理类的具体实现,可以通过代理类来访问具体的业务逻辑,但是这种做法,一旦要具体实现要改话,那么代理类和被代理类都要修改具体的代码,非常的不方便


3. 动态代理

对于动态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(通过 CGLIB 动态代理机制

对于 Java 来说,实现动态代理的方式有很多种,比如 JDK 的动态代理,CGLB 动态代理;依赖于动态代理机制的框架也有很多,比如 Spring AOP,RPC 框架;所以,学会动态代理对我们理解其他框架也是很有帮助的


3.1 JDK 的动态代理机制

3.1.1 实现步骤

  1. 定义一个接口及其实现类
  2. 再创建一个类实现 InvocationHandler 接口并重写 invoke 方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑。
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象

接下来通过代码演示一下:

步骤一:定义一个接口及其实现类,一个实现类

定义一个发送消息的接口

public interface MsgService {
    String send(String message);
}
public class MsgServiceImpl implements MsgService {
    @Override
    public String send(String message) {
        System.out.println("发送了一次消息");
        return message;
    }
}

步骤二:创建一个类实现 InvocationHandler 接口并重写 invoke 方法,在 invoke 方法中我们会调用被代理类的方法

动态代理类

public class MsgInvocationHandler implements InvocationHandler {

    /** 被代理的真实对象 */
    Object target;

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

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

步骤三:实际使用

public static void main(String[] args) {
    MsgService msgService = (MsgService) Proxy.newProxyInstance(
            MsgServiceImpl.class.getClassLoader(),
            MsgServiceImpl.class.getInterfaces(),
            new MsgInvocationHandler(new MsgServiceImpl()));

    msgService.send("你愁啥");
}

运行结果:

发送了一次消息

3.1.2 小结

  1. 可以看到,在 JDK 动态代理的实现中,主要是通过 Proxy.newProxyInstance() 方法,来动态的生产代理对象
  2. 通过Proxy.newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法

JDK 动态代理有它的问题,那就是只能代理实现了接口的类,为了解决这个问题,就有了 CGLIB 动态代理机制


3.2 GLIB 动态代理机制

3.2.1 实现步骤

  1. 定义一个类
  2. 自定义 MethodInterceptor 并重写 intercept() 方法,intercept() 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke() 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

接下来通过代码示例演示一下:

步骤一:引入依赖

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

步骤二:定义一个需要被代理的类

public class MsgService {
    public String send(String message) {
        System.out.println("发送了一次消息");
        return message;
    }
}

步骤三:使用 MethodInterceptor

public class MsgMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}

步骤四,实际使用:

public static void main(String[] args) {
    Class clazz = MsgService.class;
    // 创建动态代理增强类
    Enhancer enhancer = new Enhancer();
    // 设置类加载器
    enhancer.setClassLoader(clazz.getClassLoader());
    // 设置被代理类
    enhancer.setSuperclass(clazz);
    // 设置方法拦截器
    enhancer.setCallback(new MsgMethodInterceptor());
    // 创建代理类
    MsgService msgService = (MsgService) enhancer.create();

    msgService.send("你瞅啥瞅");
}

程序输出:

发送了一次消息

3.2.2 小结

  1. CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心
  2. 你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法
  3. 通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法

4. 动态代理与静态代理之间的区别

  1. 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的
  2. JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的