2

设计模式之代理模式_程序员田同学的技术博客_51CTO博客

 2 years ago
source link: https://blog.51cto.com/u_15476035/5539712
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文由老王出租房子引出——代理设计模式,将从最简单的静态代理实现开始,后延伸使用jdk实现动态代理,最后扩展到Cglib实现动态代理。为了更深入理解代理模式,我们会对实际应用中的典型案例进行介绍,包括在Spring和Mybatis中的应用。

读者可以拉取完整代码到本地进行学习,实现代码均测试通过后上传到 码云

一、引出问题

上篇文章老王和小王组装电脑虽然完美结束了,但是老王和小王的争吵却并没有结束。老王决定将小王扫地出门,并把小王住的房子出租,租金用来弥补游戏本的花销。

老王花费很大的功夫,搞清楚了各种租房平台的规则并发布了房源信息,接着邻居提醒他:房子租出去并不代表躺着收租金了,有一天租客提出一些额外的要求,在合同允许的范围内,你也要尽量满足他们(为了便于理解,现实当然不存在啦),房子租出去后物业有问题你还要和物业协调。

老王开始思考,如果我直租给租客,会面临两个问题:

①我需要了解租房的全过程,我自己的事和租房的事严重的耦合了。

②租客提出的一些要求我不得不介入到其中,我不得不改变我自己的行程安排。

我应该想到一种办法,

第一点,将业务和功能解耦,业务层专注业务,比如网络接口请求,业务层只需要知道该调哪个接口请求方法,而不需要知道这个接口请求是如何发起网络请求的。

第二点,创建一个切面,在这个切面中增加一些通用的附加操作,比如注解解析,日志上报等,避免这些通用操作在每个接口方法都要写一遍。

老王灵感一闪:我可以给我的房子找一个一个代理,以控制对这个房子的管理。即通过代理管理房子.这样做的好处是:可以在目标实现的基础上,增强额外的功能操作,即扩展目标的功能。

这实际上就是静态代理。

二、静态代理

代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

也即在静态代理中应该有三个角色:

①代理对象,消费端通过它来访问实际的对象(中介)

②实际被代理的对象(老王房子)

③一组可以被代理的行为的集合,通常是一个接口(老王和中介之间的约定事件)

老王与中介的约定接口:

/**
 * 代理行为的集合(接口)
 * @author tcy
 * @Date 02-08-2022
 */
public interface HostAgreement {

    // 房子出租
    void rent();
}

实际对象类(老王):

/**
 * 目标对象
 * @author tcy
 * @Date 02-08-2022
 */
public class Host implements HostAgreement {

    /**
     * 目标对象的原始方法
     */
    @Override
    public void rent() {
        System.out.println(" 这个房子要出租...");
    }
}

代理类(中介):

/**
 * 实际对象的代理
 * @author tcy
 * @Date 02-08-2022
 */
public class HostProxy implements HostAgreement {

    // 目标对象,通过接口来聚合
    private HostAgreement target;

    //构造器
    public HostProxy(HostAgreement target) {
        this.target = target;
    }

    @Override
    public void rent() {
        System.out.println("房子出租前,装修一下....");
        target.rent();
        System.out.println("房子出租后,与物业协调....");//方法
    }

}
/**
 * @author tcy
 * @Date 02-08-2022
 */
public class Client {

    public static void main(String[] args) {
        //创建目标对象(被代理对象)
        Host hostTarget = new Host();
        //创建代理对象, 同时将被代理对象传递给代理对象
        HostProxy hostProxy = new HostProxy(hostTarget);
        //通过代理对象,调用到被代理对象的方法
        hostProxy.rent();
    }

}

这样就很好的解决了老王想到的问题,老王心满意足的看着自己的成果。

但中介看着老王的方案开始小声的嘀咕了,我一个人管那么多的房子,每一个房东都让我实现一个代理类,那我就会有很多的代理类,这是个问题呀!还有就是,有一天协议变动了,我们俩都要做许多工作。

最好是不要让我实现我们之间的协议(接口)了。

老王开始改造他的方案了。

三、动态代理Jdk

老王突然想到,使用jdk的动态代理可以很好的解决这个问题。

Jdk代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象动态代理,也叫做:JDK代理、接口代理。

我们对代码进行改造。

目标对象和目标对象的协议保持不变,我们需要修改也就是中介(代理类)的代码。

/**
 * 代理类
 * @author tcy
 * @Date 02-08-2022
 */
public class HostProxy {

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

    //构造器 , 对target 进行初始化
    public HostProxy(Object target) {

        this.target = target;
    }

    //给目标对象 生成一个代理对象
    public Object getProxyInstance() {

        //说明
      /*
       *  //1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
            //2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
            //3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
       */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {

                    /**
                     * 该方法会调用目标对象的方法
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("房子出租前,装修一下....");
                        //反射机制调用目标对象的方法
                        Object returnVal = method.invoke(target, args);
                        System.out.println("房子出租后,与物业协调....");
                        return returnVal;
                    }
                });
    }

}
/**
 * @author tcy
 * @Date 02-08-2022
 */
public class Client {

    public static void main(String[] args) {

        //创建目标对象
        HostAgreement hostAgreement = new Host();
        //给目标对象,创建代理对象, 可以转成 ITeacherDao
        HostAgreement hostProxy = (HostAgreement)new HostProxy(hostAgreement).getProxyInstance();
        // proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
        //通过代理对象,调用目标对象的方法
        hostProxy.rent();
    }

}

这样就很好的解决了中介实现协议(接口)的问题,无论房子怎么变化,中介都能很完美的实现代理。

中介想让老王给他讲讲,Proxy.newProxyInstance()怎么就能完美的解决这个问题了。

老王撸起袖子开始给他讲实现原理。

在我们用Proxy.newProxyInstance实现动态代理的时候,有三个参数,第一个便是classloader。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

在我们的代码中classloader就是目标对象的类加载器。

第二个参数是 目标对象实现的接口类型,使用泛型方法确认类型,我们使用java的反射target.getClass().getInterfaces()获取到了接口类型。

第三个参数是实现InvocationHandler接口,并实现它唯一的方法invoke(),invoke其实就会执行我们的目标方法,我们就可以在invoke前后去做一些事。比如,房子出租前,装修一下或者房子出租后,与物业协调。

中介听完心满意足的离开了,老王总觉得哪里不对,中介都从协议中抽出来了,那我为什么还要被协议约束着呢?我何不也从协议(接口)中抽离出来。

我们查阅书籍觉得Cglib或许能帮到他。

四、动态代理Cglib

1、概念及实现

CGLIB是一个强大的、高性能的代码生成库。采用非常底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强,其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

老王觉得这些概念都不说人话,不如老王直接着手改造项目。

CGLIB是一个第三方的类库,首先需要引入依赖。

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

现在只需要两个角色即可,代理类和目标类。

/**
 * @author tcy
 * @Date 02-08-2022
 */
public class Host {
    /**
     * 租房方法
     */
    public void rent() {

        System.out.println("这个房子要出租...");

    }
}
/**
 * 代理类
 * @author tcy
 * @Date 02-08-2022
 */
public class HostProxy implements MethodInterceptor {

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

    //构造器,传入一个被代理的对象
    public HostProxy(Object target) {
        this.target = target;
    }

    //返回一个代理对象:  是 target 对象的代理对象
    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        System.out.println("房子出租前,装修一下....");
        Object returnVal = method.invoke(target, args);
        System.out.println("房子出租后,与物业协调....");
        return returnVal;

    }

}
/**
 * @author tcy
 * @Date 02-08-2022
 */
public class Client {
    public static void main(String[] args) {

        //创建目标对象
        Host HostTarget = new Host();
        //获取到代理对象,并且将目标对象传递给代理对象
        Host Hostproxy = (Host)new HostProxy(HostTarget).getProxyInstance();
        //执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
        Hostproxy.rent();
    }

}

现在不仅老王和中介都不需要实现接口了,而且完美的实现了他们之间的功能。

jdk和CGLIB实现动态代理的区别我们对比一下:

2、JDK动态代理与CGLIB对比

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才生成代理对象。

CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。

JDK Proxy的优势:

最小化依赖关系、代码实现简单、简化开发和维护、JDK原生支持,比CGLIB更加可靠,随JDK版本平滑升级。而字节码类库通常需要进行更新以保证在新版Java上能够使用。

基于CGLIB的优势:

无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量。高性能。

静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib。

为了让代理模式理解的更加深刻,我们来看代理模式在两个经典框架SpringAop和Mybtis中的应用。

五、典型应用

1、在SpringAop的运用

动态代理一个显著的作用就是,在不改变目标对象的前提下,能增强目标对象的功能,这其实就是AOP的核心。

AOP(Aspect Oriented Programming)是基于切面编程的,可无侵入的在原本功能的切面层添加自定义代码,一般用于日志收集、权限认证等场景。

SpringAop同时实现了Jdk的动态代理和Cglib的动态代理。

运用动态代理直接作用到需要增强的方法上面,而不改变我们原本的业务代码。

2、在MyBatis的运用

在Mybitis实现的是Jdk的动态代理。

源码中有一个MapperProxyFactory类,其中有一个方法。

//构建handler的过程。
protected T newInstance(MapperProxy<T> mapperProxy) {
  //标准的类加载器,接口,以及invocationHandler接口实现。
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

Proxy.newProxyInstance()正是我们在Jdk动态代理中的使用。

结合典型应用,认真体会动态代理设计模式,参考 软件设计七大原则 在实际应用中更加灵活的使用,不生搬硬套。

为了丝毫加入老王的故事,推荐你看前面的三篇创建型设计模式,以做到丝滑入戏。

 一、设计模式之工厂方法和抽象工厂

 二、设计模式之单例和原型

 三、设计模式之建造者模式


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK