设计模式之代理模式

135 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

其实,我们平时业务代码中不常见代理模式,但是源码中代理模式还是比较常用的。学习代理模式能更好的理解源码,对于一些问题的排查也是有些帮助的。本次就对代理模式的定义和使用方式进行简单的介绍。

代理模式

有时候我们不能直接操作目标对象,只能去用代理对象间接的的操作目标对象。

举个例子,王者荣耀在座的应该没有几个没有打过的吧,游戏代打这个职业应该也不陌生吧。而这个游戏代打的人就相当于我们的代理对象去给我们的游戏账号上分。

那么,有哪些代理模式呢。大致分为两种

  • 静态代理
  • 动态代理

代理一般分为三个角色

  • 抽象主题角色 就是目标对象的抽象类或者接口
  • 具体主题角色 就是需要代理的目标对象
  • 代理主题角色 就是代理类

我们在详细聊代理模式之前,首先编写一些代码,供下面引用

我们首先定义一个开发语言的接口

interface DevelopLanguage {

    String canDo(String what);
}

再来一个Java语言的实现

class Java implements DevelopLanguage {

    private String name;

    public Java() {
    }

    public Java(String name){
        this.name = name;
    }

    @Override
    public String canDo(String what) {
        return name + "可以做" + what;
    }
}

静态代理

静态代理就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

简单来讲就是将代理对象组合到代理对象中,直接就可以使用。

我们用代码来看看静态代理的简单实现

class JavaStaticProxy implements DevelopLanguage{

    private Java java;

    public JavaStaticProxy(Java java) {
        this.java = java;
    }

    @Override
    public String canDo(String what) {
        return java.canDo(what);
    }
}

可以看到代理类需要实现开发语言的接口,并且将需要代理的目标对象组合到类中,直接就可以调用目标对象的目标方法,也就实现了简单的静态代理。

动态代理

来看看动态代理,动态带来目前分为两种实现方式

  • JDK实现
  • CGLib实现

基于JDK实现

JDK动态代理是基于接口实现的,不可以基于实现类

JDK动态代理基础的语法是

Proxy.newProxyInstance(ClassLoader loader,
                        Class<?>[] interfaces,
                        InvocationHandler h)

newProxyInstance就是一个静态方法

loader是接口的类加载器

interfaces是接口的class数组

h是InvocationHandler这个接口,我们如何代理对象的实现就是要去实现这个接口

这些参数说完了,那我用代码看看如何实现动态代理吧

class LanguageJDKProxy{

    private DevelopLanguage language;

    public LanguageJDKProxy(DevelopLanguage language){
        this.language = language;
    }
    
    // 获取动态代理对象
    public DevelopLanguage newProxyInstance(){
        return (DevelopLanguage) Proxy.newProxyInstance(DevelopLanguage.class.getClassLoader(), new Class[]{DevelopLanguage.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(language,args);
            }
        });
    }
}

基于CGLib实现

CGlib实现动态代理是可以基于实现类去实现的

实现步骤

  • 创建Enhancer类
  • 为Enhancer设置目标对象的class
  • 为Enhancer设置回调接口实现(MethodInterceptor的实现)
  • 最后调用create方法返回代理对象

写个代码看看

class LanguageCGlibProxy{

    private Java java;

    public LanguageCGlibProxy(Java java){
        this.java = java;
    }
    public Java newProxyInstance(){

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Java.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                return method.invoke(java,objects);
            }
        });

        return (Java) enhancer.create();
    }
}

为啥JDK只能基于接口实现动态代理

通过父类 Proxy 的构造方法,保存了创建代理对象过程中传进来的 InvocationHandler 的实例,使用 protected 修饰保证了它可以在子类中被访问和使用。但是同时,因为 Java 是单继承的,因此在继承了 Proxy 后,只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的

写个main方法测试一下

public static void main(String[] args) {

    /*************************** 静态代理 ****************************/
    JavaStaticProxy javaStaticProxy = new JavaStaticProxy(new Java("Java static proxy"));
    System.out.println(javaStaticProxy.canDo("一点事"));


    /*************************** JDK实现动态代理 不需要接口****************************/

    DevelopLanguage language = new Java("Java JDK proxy");

    DevelopLanguage languageJDKProxy = new LanguageJDKProxy(language).newProxyInstance();

    System.out.println(languageJDKProxy.canDo("任何事"));

    /*************************** Cglib实现动态代理 不需要接口****************************/

    Java java = new Java("Java cglib proxy");
    Java javaProxy = new LanguageCGlibProxy(java).newProxyInstance();
    System.out.println(javaProxy.canDo("any things"));

}

image.png

参考书籍、资料