面向对象编程内功心法系列十(聊一聊代理模式)

1.引子

代理,咱们都很熟悉,非要举一个例子的话,好比说生活中的各类中介,好比说明星艺人的经纪人,都是代理,生活中的代理。java

延伸到程序员的世界中也有代理,咱们都很是熟悉代理设计模式。好比说提到spring框架,你必定会想起IOC,和AOP。IOC叫作控制反转,是工厂设计模式和依赖注入的应用。AOP叫作面向切面编程,相应的应用了代理设计模式。程序员

这么看来代理两个字还有许多讲究,那么你有想过代理设计模式,到底解决了什么问题吗?或者说日常咱们租房、买房为何非要找中介呢?spring

在代理这种设计模式中,咱们能够尝试问这么一些问题编程

  • 谁是代理对象?设计模式

  • 谁是被代理(目标对象)?安全

  • 若是没有代理对象,目标对象会怎么样?框架

举个例子,某歌星须要开一场演唱会,须要作许多准备工做。好比说搭台、商务签约、行程安排、最后开唱。咱们常说术业有专攻,对于一个歌手,他擅长于也仅擅长于唱歌,别的不会。jvm

那怎么办?请经纪人呐!像商务签约、行程安排都交给经纪人来作,歌星只须要到点登台唱歌便可。你看这就是代理,咱们能够用正式一点的话语来总结描述一下ide

  • 代理是一种机制,一种用于控制访问目标对象的机制性能

  • 代理,能够实现对目标对象的保护(要找歌星签名,先得过经纪人这一关)

  • 代理,能够实现对目标对象的加强(歌星只须要关心唱歌,别的事情交给经纪人)

到这里,你应该可以充分理解代理,及代理设计模式了。

那到底什么是代理设计模式呢?

  • 代理设计模式,它是一种结构型设计模式

  • 有代理对象,被代理对象(目标对象)

  • 经过代理对象,能够实现对目标对象的访问控制,增长目标对象安全性

  • 经过代理对象,能够实如今不改变目标对象的的状况下,加强目标对象能力

2.案例

在引子部分,咱们搞清楚了什么是代理设计模式,接下来我将经过案例实现,给你演示一下代理设计模式的应用。

关于代理设计模式,你须要留意它有两种实现方式,分别是

  • 静态代理

  • 动态代理

另外在实现的过程当中,一般须要代理对象,与被代理对象实现相同的接口,即它们是同类。咱们具体看代码案例吧,相信看到代码你就明白了。

2.1.静态代理

一般在项目中,咱们将功能分为

  • 业务功能(好比说用户、订单的增删改查)

  • 非业务功能(好比说事务控制、记录日志、性能耗时统计)

对于业务开发工程师来讲,只须要关注业务功能的开发。而非业务功能,在每一个项目中都差很少,具有必定的通用性,彻底能够经过代理设计模式来支持实现。

接下里我将给你模拟一个这样的案例,咱们以保存用户接口为例,在保存用户的同时进行接口耗时统计。

2.1.1.接口

/**
 * 用户接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:09
 */
public interface UserService {

    /**
     * 保存用户接口
     * @param name
     */
    void saveUser(String name);
}

2.1.2.被代理类

/**
 * 用户接口实现类
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:10
 */
public class UserServiceImpl implements UserService {

    /**
     * 保存用户接口
     * @param name
     */
    @Override
    public void saveUser(String name) {
        System.out.println("保存用户:" + name);
    }
}

2.1.3.代理类

/**
 * 用户接口代理,与目标对象实现相同的接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:12
 */
public class UserProxy implements UserService{

    private UserService target;

    public UserProxy(UserService target){
        this.target = target;
    }

    /**
     * 保存用户接口(代理)
     * @param name
     */
    @Override
    public void saveUser(String name) {
        // 统计耗时开始(加强的功能)
        System.out.println("统计保存用户接口耗时.start:" + System.currentTimeMillis());

        // 调用目标对象
        target.saveUser(name);

        // 统计耗时结束(加强的功能)
        System.out.println("统计保存用户接口耗时.end:" + System.currentTimeMillis());
    }
}

2.1.4.测试类

public static void main(String[] args) {
  // 建立目标对象
  UserService target = new UserServiceImpl();

  // 建立代理对象
  UserService proxy = new UserProxy(target);

  // 经过代理对象,实现保存用户的时候,统计耗时
  proxy.saveUser("小明");
}

#执行结果
统计保存用户接口耗时.start:1615105035485
保存用户:小明
统计保存用户接口耗时.end:1615105035485

Process finished with exit code 0

2.1.5.总结分析

UserProxy类有两个特色

  • 实现UserService接口,与目标对象实现相同的接口

  • 持有UserSerivceImpl实例,经过组合实现目标对象的能力加强

  • 你须要关注UserProxy中的saveUser方法,在保存用户target.saveUser(name)先后,进行耗时统计

最后经过执行结果,咱们看到在不修改UserSerivceImpl类代码的状况下,实现了保存用户的同时,进行接口耗时统计能力的加强。你看这就是代理设计模式,代码实现比较简单。

不过你须要注意,在实际项目中,静态代理使用很是少,缘由是静态代理方式,每个目标类都须要相应定义一个代理类,致使类的数量会膨胀,另外代码维护性比较差

那么针对静态代理存在的问题,咱们该如何解决呢?答案是动态代理

2.2.动态代理

经过静态代理示例代码,咱们比较直观的看到了代理设计模式的实现。不过咱们说静态代理存在两个问题

  • 每个目标类,都须要一个相应的代理类,类的数量膨胀问题

  • 静态代理,代码是在编译时实现,代码维护性比较差

针对静态代理的问题,jdk提供了动态代理的语法支持,接下来咱们一块儿来看动态代理示例代码,你须要关注两个类

  • Proxy,建立代理对象入口类

  • InvocationHandler,具体实现代理加强的接口类

2.2.1.经过动态代理,建立代理类

/**
 * 动态代理示例
 * 1.Proxy,建立代理对象入口类
 * 2.InvocationHandler,具体实现代理加强的接口类
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/7 16:33
 */
public class DynamicUserProxy {

    public static void main(String[] args) {
        // 建立目标对象
        UserService target = new UserServiceImpl();

        // 建立代理对象
        UserService proxy = (UserService)Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 统计耗时开始
                        System.out.println("统计保存用户接口耗时.start:" + System.currentTimeMillis());

                        // 调用目标对象
                        Object result = method.invoke(target, "小明");

                        // 统计耗时结束
                        System.out.println("统计保存用户接口耗时.end:" + System.currentTimeMillis());

                        return result;
                    }
                }
        );

        // 经过代理对象,实现保存用户的时候,统计耗时
        proxy.saveUser("小明");
    }
}


#执行结果
统计保存用户接口耗时.start:1615106267280
保存用户:小明
统计保存用户接口耗时.end:1615106267280

Process finished with exit code 0

2.2.2.总结分析

对比动态代理,静态代理

  • 执行结果是同样的,都在不修改目标类UserServiceImpl的状况下,实现了接口耗时统计能力的加强

  • 代码结构都遵循了三个步骤

    • 建立目标对象

      // 建立目标对象
      UserService target = new UserServiceImpl();

       

    • 建立代理对象

      #静态代理
      // 建立代理对象
      UserService proxy = new UserProxy(target);
          
      #动态代理
      // 建立代理对象
      UserService proxy = (UserService)Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 统计耗时开始
                System.out.println("统计保存用户接口耗时.start:" +
                                   System.currentTimeMillis());
      
                 // 调用目标对象
                 Object result = method.invoke(target, "小明");
      
                 // 统计耗时结束
                 System.out.println("统计保存用户接口耗时.end:" + 
                                    System.currentTimeMillis());
      
                 return result;
               }
            }
      );

       

    • 经过代理对象,实现功能加强

    // 经过代理对象,实现保存用户的时候,统计耗时
    proxy.saveUser("小明");

     

  • 差别最大的地方,是在建立代理对象。动态代理中,咱们经过Proxy的newProxyInstance方法,建立代理对象,它有三个参数

    • 参数1:类加载器,动态代理的本质是运行时实现加强,即在运行的时候jvm动态生成代理类字节码Class,从而实例化代理对象。所以,须要类加载器

    • 参数2:目标对象实现的接口列表,符合代理设计模式定义,代理类,与目标类实现相同的接口

    • 参数3:InvocationHandler,用于实现加强的接口,关心接口中的invoke方法,该方法中实现代理逻辑

经过上面动态代理,静态代理的对比,而且我详细给你解释了动态代理相关的代码细节,相信你能够很好的理解代理设计模式、静态代理、动态代理了。

关于代理模式,是一个很重要,且用的比较多的设计模式。最后我想抛出一个知识点,基于jdk提供的Proxy动态代理实现,咱们叫作基于接口的动态代理,它的意思是说目标类必需要实现接口。那么若是目标类在没有实现任何接口的状况下,如何实现动态代理呢?答案是cglib,抛砖引玉指望你能够去了解一下

相关文章
相关标签/搜索