前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式-代理模式

设计模式-代理模式

作者头像
码哥字节
发布2020-03-24 15:14:16
3650
发布2020-03-24 15:14:16
举报
文章被收录于专栏:Java 技术栈

为另一个对象提供代表,以便控制客户对对象的访问。其定义为:为另一个对象提供替身或占位符以访问这个对象。具体地吗可以浏览 https://github.com/UniqueDong/zero-design-patterns

什么是代理

官话上讲是一种设计模式,目的是希望代码重用。跟我们以往访问对象的方式不同,代理模式不是直接通过目标对象,而是通过代理访问我们的目标对象以及方法。因为有的时候我们无法直接与目标对象建立联系或者,我们要控制客户端访问。所以便通过代理来访问我们的真实对象。

就好比「客户」-> 「明星经纪人」-> 「明星」。我们不是直接与明星联系,明星很忙的,要唱歌跳舞烫头拍电影,给的价格足够好,经纪人才告知明星接下这个任务。

主要角色

代理模式

  • Subject:被代理类与代理类都要实现的主题接口。
  • ConcreteSubject:被代理类,也叫委托类,也就是真实对象,真正干活的。
  • Proxy:代理类,扮演者中介的角色,控制客户端对真实对象的访问、生成代理对象让客户端透明调用,通过代理我们可以屏蔽或者加工针对真实对象。

使用场景

  • 通常更多出现在我们常用的开源框架里面,比如 Mybatis 中我们 通过Mapper 接口就能调用到真正执行 SQL 的逻辑,其本质就是利用了动态代理,定位到真实的Statement执行。
  • Spring AOP 也是利用了动态代理,比如 Spring 事务,当调用的方法是被 Spring 事务管理的时候,其实他会生成一个代理类,封装我们的事务处理逻辑从而实现了事务增强,代码复用。解放我们开启事务、执行逻辑 、提交事务或者回滚事务的模板代码,我们只要安心的编写路基代码即可。在这里一共用到了 「模版方法模式」、动态代理模式。关于「模版方法」模式可以参考历史文章。
  • 还有 Dubbo RPC 框架,也是有使用动态代理。对于消费者我们只是通过接口就能调用提供者所实现的功能,就像本地调用一样。它为我们封装了网络传输、序列化、解码编码的繁琐细节。就是生成了一个代理类为屏蔽了底层细节。使得我们可以透明调用。

我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。

静态代理代码示例

我们按照 UML 类图来实现一个简单的静态代理模式,首先先创建一个 Subject 接口。

代码语言:javascript
复制
public interface Subject {

    public void request();
}

创建一个真实对象去实现该接口

代码语言:javascript
复制
public class RealSubjbect implements Subject {

    @Override
    public void request() {
        System.out.println("this is RealSubjbect.request()");
    }
}

创建我们的代理类,持有真实对象的引用,同时实现了 Subject 接口。

代码语言:javascript
复制
public class ProxySubject implements Subject {

    private Subject realSubjbect = null;

    /**
     * 除了代理真实角色做该做的事,代理角色提供附加操作
     * 如
     */
    @Override
    public void request() {
        preRequest(); //真实角色操作前的附加操作

        if (realSubjbect == null) {
            realSubjbect = new RealSubjbect();
        }
        realSubjbect.request();

        postRequest();//真实角色操作后的附加操作
    }
    /**
     *	真实角色操作前的附加操作
     */
    private void postRequest() {
        System.out.println("真实角色操作后的附加操作");

    }

    /**
     *	真实角色操作后的附加操作
     */
    private void preRequest() {
        System.out.println("真实角色操作前的附加操作");

    }
}

编写我们的客户端接口,通过代理类调用。

代码语言:javascript
复制
public class Client {

    public static void main(String[] args) {
        Subject subject = new ProxySubject();
        subject.request(); //代理者代替真实者做事情
    }
}

打印的结果如下所示,我们实现了对真实对象的控制,并且新增一些操作。就像Spring的AOP实现的功能一样。

代码语言:javascript
复制
真实角色操作前的附加操作
this is RealSubjbect.request()
真实角色操作后的附加操作

缺点

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

另外,如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

动态代理

概念

动态代理类的源码是在程序运行期间 JVM 根据反射机制动态生成的,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

实现方式

主要有两种实现方式:

  1. 使用 JDK 实现。委托类逆序有接口。
  2. 使用 CGLIB 实现。不能是final修饰的类,只能修饰 public 方法。

JDK 动态代理

UML 类图如下所示

JDK动态代理

  • 创建一个自己Handler 实现 InvocationHandler,同时持有真实对象的引用,会依赖我们的业务类。
  • 代理对象通过 Proxy.newProxyInstance()生成,而该方法以来于 Handler对象和 真实对象 。

通过类图其实我们也可以知道,当通过动态代理生成的代理字节码调用的时候就会委托到 Handler 的 invoke 方法。同时 Handler 又持有真正的业务对象。所以能在执行调用真实的对象之前控制其行为以及访问。主要优点:

  • 隐藏委托类的实现,调用者只需要和代理类进行交互。
  • 解耦合,在不改变委托类代码情况下做额外处理。比如添加 Dubbo 客户端调用时的序列化、网络传输细节。

代码实现

通过类图,创建 JDK 动态代理我们可以分为三步。

  1. 定义被代理的业务接口以及实现类。
  2. 新建自定义的 InvocationHandler 实现 InvocationHandler接口,并依赖被代理类(真实对象的引用)。
  3. 通过 Proxy.newProxyInstance() 方法创建具体代理对象。

现在我们有一个需求,为我们的业务逻辑统一记录调用日志,或者事务控制,在这里我们那就编写一个日志记录为我们的被代理类记录日志。模拟类似 Spring AOP功能,一个简化版的例子让大家明白其使用场景以及原理。

第一步:我们定义自己的业务接口 OrderServiceProductService

代码语言:javascript
复制
public interface OrderService {
    String createOder(String no);
}

public interface ProductService {
    String getProduct(String no);
}

业务逻辑的实现类

代码语言:javascript
复制
public class OrderServiceImpl implements OrderService {
    @Override
    public String createOder(String no) {
        return "生成订单" + no + "成功";
    }
}

public class ProductServiceImpl implements ProductService {
    @Override
    public String getProduct(String no) {
        return "获取商品" + no + "成功";
    }
}

第二步:定义我们的 Handler 实现 JDK 的 Invocationhandler,将持有的委托引用定义 Object 类型,这样可以代理所有需要日志记录的委托类,同时我们有一个 bind()方法,用于设置 target 同时返回 对应的代理类让客户端调用。第三步的 Proxy.newProxyInstance() 我们放在 bind 方法里。代码更加简洁。

代码语言:javascript
复制
public class LoggerInterceptor implements InvocationHandler {

    /**
     * 持有委托类的引用
     */
    private Object target;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(proxy.getClass().getInterfaces()[0]);
        //通过反射调用
        System.out.println("Entered " + target.getClass().getName() + "-" + method.getName() + ",with arguments{" + args[0] + "}");
        //调用目标对象的方法
        Object result = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("执行结果:" + result.toString());
        System.out.println("共耗时:" + (end - start));
        return result;
    }

    /**
     * 获取代理类:并将 target 委托类绑定到 我们定义的Targe中
     * @param target 真实的委托类
     * @return
     */
    public synchronized Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

最后我们新建启动类看效果,分别有两个业务逻辑类需要日志记录。具体可看代码注释

代码语言:javascript
复制
public class BootStrap {
    public static void main(String[] args) {
      // 新建业务逻辑
        OrderService orderService = new OrderServiceImpl();
        ProductService productService = new ProductServiceImpl();
			// 新建我们的 日志 Handler
        LoggerInterceptor loggerInterceptor = new LoggerInterceptor();

      //绑定 委托类同时生产代理类调用 。
        OrderService orderServiceProxy = (OrderService) loggerInterceptor.bind(orderService);
        orderServiceProxy.createOder("12927381");

        ProductService productServiceProxy = (ProductService) loggerInterceptor.bind(productService);
        productServiceProxy.getProduct("34010234");
    }
}

结果打印如下所示:我们可以看到,每个我们所代理的接口执行方法前后都打印了日志以及返回结果。其实 Spring AOP 就是通过动态代理实现。

代码语言:javascript
复制
nterface com.zero.headfirst.proxy.service.OrderService
Entered com.zero.headfirst.proxy.service.OrderServiceImpl-createOder,with arguments{12927381}
执行结果:生成订单12927381成功
共耗时:2
interface com.zero.headfirst.proxy.service.ProductService
Entered com.zero.headfirst.proxy.service.ProductServiceImpl-getProduct,with arguments{34010234}
执行结果:获取商品34010234成功
共耗时:1

CGLIB 动态代理

CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

不能是 final 修饰的类,以及final方法,可以不定义接口。

使用方式很简单:我们要先添加cglib 依赖 ,新建一个类实现 MethodInterceptor 。

代码语言:javascript
复制
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggerProxy implements MethodInterceptor {


    /**
     * 创建代理类
     * @param targetClass 委托类
     * @return
     */
    public Object bind(Class targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        //设置回调方,当客户端通过代理调用方法的时候会会调用我们重写的 intercept() 方法
        enhancer.setCallback(this);
        //创建代理类
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long start = System.currentTimeMillis();
        //通过反射调用
        System.out.println("Entered " + o.getClass().getName() + "-" + method.getName() + ",with arguments{" + args[0] + "}");
        Object result = methodProxy.invokeSuper(o, args);
        long end = System.currentTimeMillis();
        System.out.println("执行结果:" + result.toString());
        System.out.println("共耗时:" + (end - start));
        return result;
    }
}

是不是很简单?新建 Enhancer ,只要设置好 委托类以及回调类。在这里我们可以利用工厂模式 创建不同的代理类对应的回调。这里简单实例就不写了。

接下来看我们的测试类以及结果

代码语言:javascript
复制
public class CglibBootStrap {
    public static void main(String[] args) {

        LoggerProxy loggerProxy = new LoggerProxy();

        OrderService orderService = (OrderService) loggerProxy.bind(OrderServiceImpl.class);
        orderService.createOder("12873051209g");


        ProductService productProxy = (ProductService) loggerProxy.bind(ProductServiceImpl.class);
        productProxy.getProduct("2780935782309");


    }
}

打印结果如下所示

代码语言:javascript
复制
Entered com.zero.headfirst.proxy.service.OrderServiceImpl$$EnhancerByCGLIB$$54d983a1-createOder,with arguments{12873051209g}
执行结果:生成订单12873051209g成功
共耗时:14
Entered com.zero.headfirst.proxy.service.ProductServiceImpl$$EnhancerByCGLIB$$4e2c7c36-getProduct,with arguments{2780935782309}
执行结果:获取商品2780935782309成功
共耗时:5
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码哥字节 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是代理
  • 主要角色
  • 使用场景
  • 静态代理代码示例
    • 缺点
    • 动态代理
      • 概念
        • 实现方式
          • JDK 动态代理
            • 代码实现
              • CGLIB 动态代理
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档