代理模式

6,708 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第31天,点击查看活动详情

引言

在做应用的时候经常在文章、日常的开发中听到 Spring 的核心特色是 AOP 和 IOC,随着经验和知识的积累,所以今天就来刨根问底,去探究一下,Spring AOP 到底如何实现的,系列文章包含以下内容:

  • Java 中代理模式——静态代理和动态代理
  • JDK 动态代理和 CGLIB
  • Spring AOP 源码分析

代理模式

静态代理

说起静态代理模式,我还能记得刚开始接触代码的时候读的《大话设计模式》,小菜和大鸟关于娇娇的恋爱故事所抽象出的设计模式。所以关于静态代理的设计模式,我想致敬经典,将原来的 C# 代码的代码模式用 Java 重写一下。

静态代理UML

proxy.png

静态代理逻辑实现

Subject 接口,定义了 RealSubject 和 Proxy 的公用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。

public interface Subject {
    void request();
}

RealSubject 类,定义 Proxy 所代表的真实实体。

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真实的用户请求");
    }
}

Proxy 类,保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来代替实体。

public class Proxy implements Subject {
    RealSubject realSubject;
​
    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }
​
    @Override
    public void request() {
        System.out.println("代理准备执行请求");
        realSubject.request();
        System.out.println("代理已经执行请求");
    }
}

结合 Spring 实现静态代理,采用注入的方式实现代理对象。

@Service
public class SpringStaticProxy {
    @Resource
    private Subject subject;
​
    public String request() {
        System.out.println("proxy static start");
        String result = subject.request();
        System.out.println("proxy static end");
        return result;
    }
}

JDK 动态代理

JDK 动态代理 两个重要类 Proxy and InvocationHandler

  • InvocationHandler:

    给代理实例调用的 invocation handler (调用程序)提供的一个实现接口,每一个代理实例都有一个相关的 invocation handler(调用程序),当一个代理实例调用一个方法时(Runtime),这个方法会被编码并且被派遣到这个代理实例的 invocation handler 中的 invoke 方法。

     /**
         * 处理代理实例的方法调用并且返回结果,当一代理实例,代理的对象方法被调用时,
         * 这个方法会在invocation handler(调用处理程序)上被调用,
         *
         * @param proxy  为代理实例(代理实例代理的对象方法被调用时)
         *
         * @param method 为Method实例(为运行时,代理实例所调用到代理对象所实现的接口方法)
         *
         * @param args   为Object数组(method所对应方法的参数值,当方法无参时可以为null)
         *
         * @return       为代理实例调用的方法的返回值
         */
    public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
    
  • proxy:

    /**
     * 返回特定接口的代理实例,该接口所有的方法调用,都会分发到特定的invocation handler
     *
     * @param  loader classloader用来界定代理类
     * @param  interfaces 代理类实现的接口数组
     * @param  h invocation handler 转发函数调用
     * @return 返回一个被特定classloader 定义,含有特定 invocation handler以及实现特定接口的代理实例
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                                          InvocationHandler h);
    

JDK 动态用法:

public class RealSubjectDynamicProxy implements InvocationHandler {
    //被代理对象
    Subject realSubject = null;
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //实现延迟加载
        //体现出代理一个运行时实例化的特点
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
​
        System.out.println("动态代理执行方法");
        return method.invoke(realSubject, args);
    }
​
    public static Subject newProxyInstance() {
        return (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
          RealSubject.class.getInterfaces(), new RealSubjectDynamicProxy());
    }
}

客户端实例:

    @Test
    public void test_proxy_method() {
        Subject subject = RealSubjectDynamicProxy.newProxyInstance();
        subject.request();
    }

从上述示例中可以看出动态代理有很多好处:

  • 动态代理不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中有很多,为每一个代理方法写一个代理方法也很麻烦。如果接口变动,则真实主题和代理类都要修改,不利于系统维护。
  • 使用一些动态代理的生成方法,可以在运行时制定代理类的执行逻辑,从而大大提升系统灵活性。

JDK 动态代理和 CGLIB

这一章节暂时还没深入学习研究,就先说说自己在开发中的感觉吧。

JDK 动态代理使用上相对简单,内置在 JDK 中不用引用第三方包,同样地,这也使它在实现复杂业务功能时候不够好用;CGLIB(Code Generation Library)字节码生成库,Spring 使用 CGLIB 来进行代理,相对 JDK 动态代理,功能要强大,性能会更好。

我们实际业务开发还选择了 Aspectj AOP 框架作为权限校验的检查,后续学习也会一起总结一下,各自的特点和使用场景的。

(未完,后续学习,将持续更新……)