2019独角兽企业重金招聘Python工程师标准>>>

代理模式

在日常开发中我们可以会接手一些老的项目,有时连源码都没有,或者有时候我会需要对业务逻辑做一定增强(功能扩展,如:日志、事务等),这时候我们通常不能或者不建议直接修改源码(可能根本没有源码)。在设计模式中有一种模式叫代理模式可以很好的应对上述场景,也符合了开闭原则(对扩展开放,对修改关闭),实现代码解耦(扩展部分不污染原有业务代码)。 所谓代理模式就是当我们需要增强业务逻辑时,创建一个增强的代理类,在代理类中调用原有业务逻辑实现代码,并对其作增强。 假设一场景,我们有一个业务接口

public interface HelloService {/*** 业务:用户说一句话* @param name* @param message* @return*/String say(String name, String message);}

并且有一个对应的实现类

public class HelloServiceImpl implements HelloService {@Overridepublic String say(String name, String message) {// 使程序休眠[0, 200]毫秒,模拟代码执行过程try {Thread.sleep(new Random().nextLong() & 200);} catch (InterruptedException e) {e.printStackTrace();}return String.format("%s : %s", name, message);}
}

我们测试一下这个场景

    private HelloService service;@Testpublic void proxy() {// 模拟业务接口实现类实例注入service = new HelloServiceImpl();// I、业务接口调用String r = service.say("Ashe", "今天没吃早饭");// Ashe : 今天没吃早饭System.out.println(r);}

现在需求调整了,要求接口调用都必须加日志,记录接口调用行为以及执行耗时。为了实现这一需求,我们定义一个日志代理类,来代理原有业务逻辑实现

public class HelloServiceLogProxy implements HelloService {private HelloService service;public HelloServiceLogProxy(HelloService service) {this.service = service;}@Overridepublic String say(String name, String message) {// 需求一:打印调用日志System.out.printf("调用了HelloService#say(%s, %s)方法!%n", name, message);// 需求二:计算程序执行耗时long time = System.currentTimeMillis();String r = this.service.say(name, message);System.out.printf("程序执行耗时:%d毫秒!%n", System.currentTimeMillis() - time);return r;}
}

测试一下代理的效果

    private HelloService service;@Testpublic void proxy() {// II、需求微调,接口调用都必须加日志,记录接口调用的时间以及执行耗时// 如果直接在业务代码中实现日志逻辑,那么耦合就太重了,而且如果其它业务有同样需求,不利用代码复用// 设计模式中的代理模式刚好应对这个场景,实现一个日志代理来增强业务接口,这样就不用修改业务逻辑了// 模拟代理类实例注入service = new HelloServiceLogProxy(new HelloServiceImpl());// 调用了HelloService#say(Peter, 我一天都没吃饭了)方法!// 程序执行耗时:194毫秒!r = service.say("Peter", "我一天都没吃饭了");// Peter : 我一天都没吃饭了System.out.println(r);}

这就是代理模式的作用,在不修改原有业务代码的基础上对功能作增强。 有人说调用的地方不是要修改实现类,还不是要修改,实际上在企业开发中并不建议直接用new的方式在业务代码中创建类实例,比如在Spring中我们通过IoC技术实现接口与实现类解耦,即使不使用Spring,我们也会使用工厂模式或者SPI等技术实现接口与实现类的解耦。

JDK动态代理

在上述案例中(静态代理),存在一个很明显的问题,当我们的需求二在应用到其它业务接口时,案例中提供的代理类无法增强其它业务接口,没有实现代码复用。针对这一点JDK提供了动态代理机制(基于反射技术),可以实现一个代理类,但与具体某个接口无关,也就是可以应用于任意接口。 我们先来实现一个动态代理类

public class LogProxyInvocationHandler implements InvocationHandler {private Object target;public LogProxyInvocationHandler(Object target) {this.target = target;}/*** 动态代理核心逻辑* @param proxy     不知道有啥用,不能使用* @param method    代理方法对象* @param args      代理方法参数* @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 需求一:打印调用日志(简化逻辑,忽略参数日志)System.out.printf("%s#%s方法!%n", target.getClass().getSimpleName(), method.getName());// 需求二:计算程序执行耗时long time = System.currentTimeMillis();Object r = method.invoke(target, args);System.out.printf("程序执行耗时:%d毫秒!%n", System.currentTimeMillis() - time);return r;}
}

实现InvocationHandler接口即可定义一个动态代理类,下面测试一下这个代理类

    private HelloService service;@Testpublic void dynamicProxy() {// III、上例看似完美,但实际上存在一个问题,如果其它业务也需要实现需求二,那么日志代理因为实现了HelloService接口,// 所以是无法直接给其它业务使用的,也就是说无法代码复用// 解决这一问题的办法是使用动态代理,代理类实现需求二,但不绑定固定接口,也就是可以和任意接口配合使用,从而实现代码复用HelloServiceImpl logic = new HelloServiceImpl();service = (HelloService) Proxy.newProxyInstance(logic.getClass().getClassLoader(),logic.getClass().getInterfaces(),new LogProxyInvocationHandler(logic));// HelloServiceImpl#say方法!// 程序执行耗时:211毫秒!String r = service.say("Peter", "我一天都没吃饭了");// Peter : 我一天都没吃饭了System.out.println(r);// 检查一个对象是否是代理对象assertTrue(Proxy.isProxyClass(service.getClass()));// class com.sun.proxy.$Proxy4System.out.println(service.getClass());}

通过Proxy.newProxyInstance()方法可以生成一个动态代理类,该方法接收三个参数,分别是:代理类的ClassLoader、代理接口(数组)、InvocationHandler实例(该实例通常会要求传入代理对象)。 通过测试代码,可以看出动态代理并不固定绑定在某一个接口上,这意味着可以代理任意接口,从而实现了代码复用。 当然上面的代码只是为了测试,实际我们并不会在业务逻辑里写这么一大段生成动态代理的代码,较好的做法是封装一个工厂类

public class JdkLogProxyFactory {public static final <T> T createProxyInstance(T t) {return (T) Proxy.newProxyInstance(t.getClass().getClassLoader(),t.getClass().getInterfaces(),new LogProxyInvocationHandler(t));}}

来隐藏创建动态代理类的细节

    private HelloService service;@Testpublic void dynamicProxy2() {// IV、上面的动态代理还需要封装一下,否则实际应用过程中写这么一大段代码并不合适// 定义一个动态代理对象生成工厂,隐藏动态代理创建过程service = JdkLogProxyFactory.createProxyInstance(new HelloServiceImpl());// HelloServiceImpl#say方法!// 程序执行耗时:193毫秒!String r = service.say("Peter", "我一天都没吃饭了");// Peter : 我一天都没吃饭了System.out.println(r);}

CGLib动态代理

日常开发中,JDK提供的动态代理技术已足够使用,我们也提倡面向接口开发,但在有些场景下我们会需要对类进行代理,而JDK只支持对接口实现动态代理,对类做代理,我们需要使用CGLib库来实现

public class CglibLogProxyFactory {public static final <T> T createProxyInstance(final T target) {// 这是一个工具类Enhancer enhancer = new Enhancer();// 设置父类(用于动态生成一个子类)enhancer.setSuperclass(target.getClass());// 设置回调函数enhancer.setCallback(new MethodInterceptor() {/*** 动态代理核心方法* @param o             和JDK中的一样,不知道意义但不能使用* @param method        代理方法对象* @param args          代理方法参数列表* @param methodProxy* @return* @throws Throwable*/@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println(methodProxy);// 需求一:打印调用日志(简化逻辑,忽略参数日志)System.out.printf("%s#%s方法!%n", target.getClass().getSimpleName(), method.getName());// 需求二:计算程序执行耗时long time = System.currentTimeMillis();Object r = method.invoke(target, args);System.out.printf("程序执行耗时:%d毫秒!%n", System.currentTimeMillis() - time);return r;}});// 创建子类(代理对象)return (T) enhancer.create();}}

实际上和JDK动态代理的代码非常相像,但CGLib可以同时对接口和类进行代理(据说性能好优于JDK的实现,这点倒是可以理解,前者直接通过字节码实现,后者通过反射实现)。 下面是使用CGLib实现的动态代理对一个未继承任何接口的类进行动态代理

    @Testpublic void dynamicProxy3() {// V、以上动态代理实现是JDK提供实现,存在的问题是只能代理接口,如果要代理类,需要使用CGLib库// 实际测试中发现即使方法使用protected和默认访问权限也能成功被代理UserService service = CglibLogProxyFactory.createProxyInstance(new UserService());// UserService#create方法!// 创建一个用户:account = Peter, password = 123456// 程序执行耗时:0毫秒!Long id = service.create("Peter", "123456");// 1System.out.println(id);// CGLib的代理类不能使用Proxy.isProxyClass()方法检测// class com.zlikun.jee.j007.UserService$$EnhancerByCGLIB$$7947b053System.out.println(service.getClass());}

结语

代理模式和动态代理技术是一种非常实用的模式(技术),在很多热门框架和库中都用应到,典型的像Spring、Struts等,抛开这些不说,即使日常开发中我们程序员自己也经常会用得到,所以推荐理解和掌握。

代码仓库:

  • https://github.com/zlikun-jee/effective-java
  • https://gitee.com/zlikun/effective-java

转载于:https://my.oschina.net/zhanglikun/blog/1922631

Java拾遗:007 - 代理模式与动态代理相关推荐

  1. Java内功修炼系列:代理模式及动态代理

    目录 一 代理模式 1.1 简介 1.2 代理模式角色定义 二 静态代理 2.1 介绍和实例 2.2 静态代理的缺点 三 动态代理 3.1 基于JDK原生动态代理实现 四 小结 一 代理模式 1.1 ...

  2. Java篇 - 代理模式和动态代理实现原理

    设计模式中有一种模式叫代理模式,Spring框架离不开动态代理技术,Android hook技术用到了反射 + 动态代理,Framework中我们也经常看到各种proxy,如ApplicationTh ...

  3. 代理模式、动态代理和面向方面

    代理的意思很好理解,它借鉴了我们日常所用的代理的意思:就是本来该自己亲自去做的某件事,由于某种原因不能直接做,而只能请人代替你做,这个被你请来做事的人就是代理.比如过春节要回家,由于你要上班,没时间去 ...

  4. 代理模式和动态代理模式_代理模式介绍

    代理模式和动态代理模式 代表:被选中或当选为他人投票或代理的人– Merriam-Webster . 委托模式:在软件工程中,委托模式是面向对象编程中的一种设计模式,其中,一个对象而不是执行其陈述的任 ...

  5. 【设计模式】--- 装饰器模式、静态代理模式和动态代理模式

    文章目录 1 引子 2 业务场景介绍 3 静态代理模式 4 装饰器模式 5 动态代理模式 5.1 Proxy --- 具体的代理对象生成组件 5.2 InvocationHandler --- 封装被 ...

  6. 20171030_chr_proxy 代理模式(动态代理)

    代理模式(动态代理) /20171030_chr_proxy/src/nuc/sw/dynamic/proxy/Dog.java package nuc.sw.dynamic.proxy;public ...

  7. 设计模式—代理模式以及动态代理的实现

    代理模式(Proxy Design Pattern)是为一个对象提供一个替身,以控制对这个对象的访问.即通过代理对象访问目标对象.被代理的对象可以是远程对象.创建开销大的对象或需要安全控制的对象. 一 ...

  8. java动态代理_Java代理模式及动态代理详解

    Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP.Java注解的获取.日志.用户鉴权等.本篇文章带大家了解一下代理模式.静态代理以及基于JDK原生动态代理. 代理模式 无 ...

  9. Java代理模式及动态代理详解

    本文转自:程序新视界公众号 Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP.Java注解的获取.日志.用户鉴权等.本篇文章带大家了解一下代理模式.静态代理以及基于JDK ...

最新文章

  1. 前后端分离必备工具:Swagger快速搞定(整合SpringBoot详细教程)
  2. 利用LVS+Keepalived 实现高性能高可用负载均衡服务器
  3. Jerry 的 SAP 技术交流群里讨论的技术问题都会同步到这个帖子里
  4. 全国计算机等级考试题库二级C操作题100套(第90套)
  5. linux系统信息查看命令
  6. c++ enum 给定类型_C++ 枚举类型详解
  7. hdu 4004The Frog's Games 二分查找!!!!!!!
  8. 系泊系统的设计界_如何回馈设计界
  9. 伴你装系统系列(上篇):Windows10
  10. 【毕业设计】大数据工作岗位数据分析与可视化 - python flask
  11. 2020复旦机试题:斗牛
  12. android关闭听筒模式,Android开发【06-29视频贴】切换听筒模式部分手机失效,怎么解决?...
  13. 谈一谈|Word文档图片的提取
  14. 唤醒计算机教案,智慧课堂教学软件常态化丨镇江中山路实验小学用 智慧“唤醒”课堂...
  15. 英特尔重入代工行业的底气和挑战,台积电,三星有点慌。
  16. 【愚公系列】2021年12月 Redis数据库-集群的搭建
  17. 无人驾驶车辆规划+轨迹跟踪控制学习笔记(1)
  18. C#操作word的一些基本方法(word打印,插入文件,插入图片,定位页眉页脚,去掉横线)...
  19. 【数据增强】图像训练技巧
  20. MySQL外键删除干净

热门文章

  1. html动图放大太模糊怎么调,如何解决图片放大画质失真问题
  2. kcp 介绍与源代码分析_Mendel:基于遗传隐喻的源代码推荐
  3. 用闭包的写法弹出元素的索引值
  4. 定制textField
  5. MVC判断用是否登录了平台
  6. android SDK 代理配置(东北大学)
  7. MYSQL 5.1自动安装脚本
  8. Android将ButtonBar放在屏幕底部
  9. 在SqlServer 2008中将数据导成脚本
  10. Django 如何展示Media底下的图片