代理模式

代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

如果有一个消耗大量系统资源的巨型对象(数据库),同时很多对象(客户端)偶尔需要数据, 并非总是需要。

如果一直执行着数据库是一个巨大的资源浪费,如果当客户端需要的时候才进行访问,消耗大量时间会带来很坏的体验。如何解决呢?

可以在客户端和数据库之间建立一个代理,代理将自己伪装成数据库对象, 可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。

还有一个好处,就是代理可以在客户端的请求前后添加一些操作。这样就可以在不改变源代码的情况下实现代码增强,这就是大名鼎鼎的面向切面编程,相信学习过Spring的朋友们都不陌生。

真实世界类比

信用卡和现金在支付过程中的用处相同

信用卡是银行账户的代理, 银行账户则是一大捆现金的代理。 它们都实现了同样的接口, 均可用于进行支付。 消费者会非常满意, 因为不必随身携带大量现金; 商店老板同样会十分高兴, 因为交易收入能以电子化的方式进入商店的银行账户中, 无需担心存款时出现现金丢失或被抢劫的情况。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://refactoringguru.cn/design-patterns/proxy

原理

代理模式包含如下角色:

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

抽象主题角色,就是Java中的接口,同时对应生活中的需求,或者说是操作。RealSubject就是实现类,是需求的具体业务逻辑。Proxy就是代理了,作为接口和实现类的桥梁,处理两者的全部请求,同时可以进行代码增强。

代理模式结构类图

代理模式结时序图


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/proxy.html

说到代理模式,就不得不提到Java的三种代理模式:静态代理、动态代理和cglib代理。让我们一个一个来看吧。

静态代理

静态代理需要新建一个和目标对象实现一样的代理对象接口。

接下来介绍一个非常经典的Demo,因为你在网上查阅相关代理模式的帖子、博客都有它的身影。因为写的真的很好,但是很遗憾的是已经无法找到最初的作者了,我找到最早的一篇相关文章是在14年知乎上面的一个答案,但是感觉这个人仍然不是原作者,所以就不声明了。让大家一起看看广为流传的大神制作吧!

public interface Subject   
{   
  public void doSomething();   
}

首先声明一个接口

public class RealSubject implements Subject   
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}

声明一个实现类来实现接口。一般这就是业务的实现流程。

public class SubjectProxy implements Subject
{
  Subject subimpl = new RealSubject();
  public void doSomething()
  {
     subimpl.doSomething();
  }
}

然后我们生存一个代理,同样是Subject接口的实现类,这样是为了保证代理对象和实际对象完全一致。在代理对象中的方法去调用实际对象的方法,实现控制。在方法执行的前后,可以进行任何你想要的增强,而不改动源代码。

测试

public class TestProxy 
{
   public static void main(String args[])
   {
       Subject sub = new SubjectProxy();
       sub.doSomething();
   }
}

//输出
call doSomething()

如果只有一个类,你发现好像没啥。如果有成百上千个呢?每个实际对象都需要建立一个代理类,产生大量冗余。一旦实际对象的接口新建方法,实例对象和代理类都需要修改,不易维护。所以就需要动态代理了,动态代理常见的方法有两种,一种是Jdk自带的动态代理,一种是第三方代码生成类库cglib代理。

动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。

public interface Subject   
{   
  public void doSomething();   
}
public class RealSubject implements Subject   
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}

接口和实际实现类都是一样的,但是动态代理就不需要手动创建一个一模一样的代理类了。因为Java的独特机制,可以使用映射来获取这个类的所有信息,所以可以实现动态生成相对应的代理类。

public class ProxyHandler implements InvocationHandler
{
    private Object tar;

    //绑定委托对象,并返回代理类
    public Object bind(Object tar)
    {
        this.tar = tar;
        //绑定该类实现的所有接口,取得代理类 
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
                                      tar.getClass().getInterfaces(),
                                      this);
    }    

    public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
    {
        Object result = null;
        //这里就可以进行所谓的AOP编程了
        //在调用具体函数方法前,执行功能处理
        result = method.invoke(tar,args);
        //在调用具体函数方法后,执行功能处理
        return result;
    }
}

创建代理类需要实现InvocationHandler,并重写invoke方法。在invoke方法中实现对实际对象的方法的执行和增强。使用newProxyInstance方法来进行实际对象和代理对象的绑定。

测试

public static void main(String args[])
    {
           ProxyHandler proxy = new ProxyHandler();
           //绑定该类实现的所有接口
           Subject sub = (Subject) proxy.bind(new RealSubject());
           sub.doSomething();
    }
//输出
call doSomething()

动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。那如果没有实现接口怎么办?就要使用我们接下来介绍的cglib代理。

cglib代理

cglib is a powerful, high performance and quality Code Generation Library. It can extend JAVA classes
and implement interfaces at runtime.

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个 子类对象从而实现对目标对象功能的扩展。

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。

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

还是之前的老例子,但是实现类不需要实现接口了。

public class RealSubject
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}

然后使用cglib的接口MethodInterceptor代替JDK的原生接口InvocationHandler。其他的基本上一致。

public class CglibProxy implements MethodInterceptor {

    private Object target;//维护一个目标对象

    public CglibProxy(Object target) {
        this.target = target;
        //为目标对象生成代理对象
    }

    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("数据校验");
        method.invoke(target, objects);
        System.out.println("返回值输出");
        return null;
    }
}

测试

        RealSubject target = new RealSubject();
        RealSubject proxy = (RealSubject) new CglibProxy(target).getProxyInstance();
        proxy.doSomething();

//输出
数据校验
call doSomething()
返回值输出

静态代理与动态代理的区别

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件。
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字
    节码,并加载到JVM中。
  • 静态代理需要动实现接口,动态代理对象不需要实现接口。

Cglib动态代理和JDK动态代理的区别

  • JDK动态代理要求目标对象必须实现接口,否则不能使用动态代理。Cglib动态代理不需要。
  • JDK动态不需要导入包,而Cglib动态代理需要。

静态代理和动态代理的优缺点

  • 静态代理

优点:可以在不修改目标对象的前提下扩展目标对象的功能。相较于动态代理更加简单,易懂
缺点:

  1. 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  2. 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
  • 动态代理
    优点:
  1. 可以在不修改目标对象的前提下扩展目标对象的功能。相较于静态代理更加方便,没有冗余。
  2. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception (拦截)。Cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

缺点:代码变得更加复杂,难以理解。

参考文献

Java 动态代理作用是什么?
Java三种代理模式:静态代理、动态代理和cglib代理

posted @ 2020-12-25 17:26  朱李洛克  阅读(113)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css