【设计模式】代理模式

241 阅读6分钟

本文主要介绍:通用的代理模式(即静态代理),Java动态代理,CGLib动态代理。

模式背景

在某些场景下,处于某些原因,一个系统模块不希望或者不能直接访问另一个模块。比如现有的项目下,已经有了一个接口,但是他只提供了服务。在后续的开发中,发现有一些其他的模块需要调用它,但是需要对这个接口进行一个修改。但是使用方的不同,对接口所需要的修改的形式也不同(可能模块A需要对调用添加日志拦截,模块B需要对调用进行权限校验)。此时我们就可以引入一个第三放的代理类来完成。

代理模式应该属于思想相对简单的设计模式,应用范围也极广。

定义&概念

给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

代理模式是一种对象结构型模式

原理

代理模式就是通过在系统中引入一个新的代理对象。唯一需要注意一点:在标准的设计模式中,被代理的对象需要有接口的实现。并且这种实现的方式称为静态代理

组成要素

  • 抽象主题角色(Subject)
    • 申明真实角色和代理角色的共同接口,让真实角色和代理角色符合里式替换原则,任何使用真实角色的地方都可以使用代理角色。客户端主要使用这个抽象进行编程。
  • 代理主题角色(Proxy)
    • 内部包含对真实角色的引用,来操作真实对象。有一个和真实角色一样的接口,用来可以替换真实角色。
  • 真实主题角色(RealSubject)
    • 真正的那个被代理的对象。

UML

实现

抽想主题角色

public interface Subject {
    void request();
}

真实主题角色

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("subject is working...");
    }
}

代理角色

public class Proxy implements Subject {
    // 引用的真实主题
    private Subject subject;
    public Proxy(Subject subject) {
        this.subject = subject;
    }
  	// 代码前加强
    public void preRequest() {
        System.out.println("prepare...");
    }
    @Override
    public void request() {
        preRequest();
        subject.request();
        postRequest();
    }
  	// 代码后加强
    public void postRequest() {
        System.out.println("finish...");
    }
}

客户端

Subject s = new Proxy(new RealSubject());
s.request();

装饰模式区别

代理模式主要是为真实角色增加一些全新的职责,如权限控制,缓冲处理等,这些职责和真实角色的原始职责不是同一个领域的职责。它的目的主要是控制外部对对象的访问。

装饰模式是对原有职责的一个扩展,扩展的职责属于同一领域的。它的目的主要是为对象扩充功能。

优缺点

优点

  • 协调调用者和被调用者,一定程度的对系统解耦,符合迪米特法则。
  • 对于新需求,只需要添加代理类,不要修改源代码。符合开闭原则。

缺点

  • 加入了一个代理类,增加了系统的复杂度。
  • 静态代理,一个真实对象,就需要有一个静态代理类与之对应。

扩展

代理模式一些其他的知识点

代理模式种类

代理模式可以分为很多种类(随便看看就成):

  • 远程代理
    • 不同进程(地址空间)之间的代理。进程可以分布在不同的主机上,通过网络RPC进行通信。
  • 虚拟代理
    • 创建一个资源消耗大的对象,可以先创建一个较小的代理对象,具体对象等需要的时候再创建。
  • 保护代理
    • 控制对一个对象的访问,可以给不同级别的用户提供权限。
  • 缓冲代理
    • 为一个目标操作提供临时缓存,以便更多客户端可以共享这个结果。
  • 智能引用代理
    • 记录对象被调用的次数等。

Java动态代理

上面说到,静态代理类的缺点:每一个需要被代理的实体类,都需要编写一个代理类。这样无疑会大大加重系统复杂度。

思考一下:所谓代理,我们完全可以剥离出RealSubject! 就好比商城试穿衣服,每一个人穿了衣服的人就是一个被代理的类,但是人是人,衣服是衣服,这个衣服可以给很多人去穿,我们不需要为每个人去单独私人定制造一件衣服,然后才能试穿。**我们将代理的类的功能性剥离出来,形成一个模板,然后通过传入实体对象动态的给其创建出代理类。**这样,我们只需要实现一个代理模板,就可以满足很多不同的实体对象了,而不是为这些实体对象一一创建代理类。

java有一种机制是在系统运行中动态创建代理类。使用的反射机制,通常代理的是一个接口下面的所有的类,因为他只能代理接口

实现

就是实现一个代理类,然后使用的时候,像这个代理类传入对象通过反射动态创建代理对象。

public class JavaProxy implements InvocationHandler {

    private Subject subject;

    public JavaProxy(Subject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke java proxy");
        if ("request".equals(method.getName())) {
            System.out.println("invoke request method");
            return method.invoke(subject, args);
        } else {
            System.out.println("调用的其他方法");
            return method.invoke(subject, args);
        }
    }
}

客户端

//JDK动态代理
Subject real = new RealSubject();
//传入实体
JavaProxy proxy = new JavaProxy(real);
//创建代理对象
Subject proxyClass = (Subject) java.lang.reflect.Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, proxy);
proxyClass.request();

原理

涉及java两个关键的类:

  • java.lang.reflect.Proxy

    • 用来生成代理类和对象的
  • java.lang.reflect.InvocationHandler

    • 代理的实现逻辑。

缺点

  • 要求原实体对象(RealSubject)必须实现接口。

注意事项

  • Java动态代理只能代理接口,要代理类需要使用第三方的CLIGB等类库。

CGLIB动态代理

CGLIB是一个Java字节码的生成工具,它会为原类动态生成一个被代理类的子类。

实现

也需要先实现一个抽象的代理层。

public class ProxyInterceptor implements MethodInterceptor {

    private Object object;

    public ProxyInterceptor(Object object) {
        this.object = object;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("invoke cglib proxy");
        if ("request".equals(method.getName())) {
            System.out.println("invoke request method [cglib]");
            methodProxy.invokeSuper(o, args);
        } else {
            System.out.println("调用的其他方法");
            methodProxy.invokeSuper(o, args);
        }
        return null;
    }
}

一个没有接口的被代理类

public class CGLibRealSubject {

    public void request() {
        System.out.println("cglib subject is working...");
    }
}

客户端使用

//CGLIB动态代理
ProxyInterceptor interceptor = new ProxyInterceptor(new CGLibRealSubject());
CGLibRealSubject cgLibRealSubject = (CGLibRealSubject) Enhancer.create(CGLibRealSubject.class,interceptor);
cgLibRealSubject.request();

相关代码:github.com/zhaohaoren/…

如有代码和文章问题,还请指正!感谢!