JAVA动态代理(JDK版本)
1、摘要
在代理设计模式里,代理类扮演桥接使用方和实现方的角色。使用者通过代理类获得实现类的访问权限,并通过代理类定制执行业务逻辑前、后的处理流程。
2、背景
有时候你不想直接调用实现类的方法或者将实现类的方法"包装"到业务流程中。你很自然地想到采用"新增包含实现类引用的代理类"的方式。而静态代理和动态代理都可实现"代理模式"的需求。
3、原理阐述
静态代理和动态代理模式类图
图1
图2
图1、2分别是静态和动态代理的类图。基于对业务控制粒度控制的需求,ProxyActor是有必要的。如果直接在使用时实例化Abstractor,你的业务逻辑变得很分散,降低了代码可读性同时增大了维护成本。
当Abstractor类的方法持续增加,RealActor必须相应地增加实现方法,如果被代理的功能模块很多,代理代码量不可控。那我们自然会思考:有没有办法可以在Abstractor增加接口时自适应地兼容新增接口呢?
针对"自适应"的需求,无非是代理类要感知Abstractor新增接口。Java的反射机制为我们提供这种感知路径,我们可以通过反射获取接口申明的所有方法集合,从而实现"Abstractor增加接口时自适应地兼容新增接口"的需求。所以动态代理应运而生。
实现动态代理时我们需要解决的几个问题:
1、如何获取接口申明的方法信息 -> Class.getInterfaces() 2、如何抽象代理对象对实现类方法的调用过程 -> invoke() 3、如何在调用代理类方法时实现对委托对象方法的调用 -> newProxyInstance()
3.1 如何获取接口申明的方法信息
java的反射机制很好地解决了该问题。我们假设AbstractActor为委托类,则利用AbstractActor.class.getInterfaces()即可获取到方法信息。
3.2 如何抽象代理对象对实现类方法的调用过程
既然代理类调用委托类方法时需要动态感知接口方法以及入参,java.lang.reflect.InvocationHandler#invoke(Object proxy, Method method, Object[] args)实现这层抽象逻辑。我们可以看出,invoke方法的语意为用哪些入参调用特定代理类的特定方法。
3.3 如何在调用代理类方法时实现对委托对象方法的调用
这是JDK版本动态代理最为核心的内容,这必须了解JDK动态代理的底层实现。我们先看下委托类AbstractActor:
/** * Created by fujianbo on 2018/5/20. * * @author fujianbo * @date 2018/05/20 */ public interface AbstractActor { /** * 处理方法 */ void process(); }
代理对象是真正调用委托类方法的地方,生成的动态代理类要完成以下工作:
* 获取委托类方法信息并生成包含同名方法的代理类 * 利用java.lang.reflect.InvocationHandler#invoke实现调用委托类方法
而java.lang.reflect.Proxy#newProxyInstance的职责就是完成这两个任务。
3.3.1 获取委托类方法信息并生成包含同名方法的代理类
java.lang.reflect.Proxy#newProxyInstance源码里,通过调用以类加载器和接口列表为入参的getProxyClass0()方法,生成以下类似的代理类(仅供理解,并非真实动态代理类源码):
public final class $Proxy1 extends Proxy implements AbstractActor { private InvocationHandler handler; private $Proxy1(){} public $Proxy1(InvocationHandler v){ this.handler = handler; } public void process() { Method method = Subject.class.getMethod("process", new Class[]{int.class}); return (Integer)handler.invoke(this, method, new Object[]); } }
3.3.2 实现调用委托类方法
代理类的处理逻辑交给InvocationHandler处理,使用方实现java.lang.reflect.InvocationHandler#invoke方法,从而控制委托对象方法的调用过程。
newProxyInstance方法最后通过带参数的构造函数(入参为外部定义的InvocationHandler)创建代理对象,实现调用代理对象的任意方法都会执行InvocationHandler#invoke方法。
总结
静态代理
- 优点:代理类只需关心自身业务逻辑。
-
缺点
- 代理(ProxyActor)和委托类(BaseActor)实现相同的接口,仅仅为了调用代理类时,代码重复;若接口新增方法,代理和委托类都要修改。
- 委托类只服务同一类型的代理类(如ProxyActor -> BaseActor),如果有多个代理类需要为每个代理类生成委托类,代码会无意义地膨胀。
动态代理
- 优点:相对于静态代理,接口所有申明的方法由invoke逻辑集中管理,多种代理的场景下无需再写委托类。
- 缺点:不能直接代理非接口的类,需借助CGLIB的Enhancer实现代理非接口的类(通过Enhancer的callback成员变量,实现对非接口类方法的调用)。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
webpack配置
webpack配置 配置 Webpack 的方式有两种: 通过一个 JavaScript 文件描述配置,例如使用 webpack.config.js 文件里的配置; 执行 Webpack 可执行文件时通过命令行参数传入,例如 webpack --devtool source-map。 这两种方式可以相互搭配,例如执行 Webpack 时通过命令 webpack --config webpack-dev.config.js 指定配置文件,再去 webpack-dev.config.js 文件里描述部分配置。 按照配置所影响的功能来划分,可分为: Entry 配置模块的入口; Output 配置如何输出最终想要的代码; Module 配置处理模块的规则; Resolve 配置寻找模块的规则; Plugins 配置扩展插件; DevServer 配置 DevServer; 其它配置项 其它零散的配置项; 整体配置结构 整体地描述各配置项的结构; 多种配置类型 配置文件不止可以返回一个 Object,还有其他返回形式; 配置总结 寻找配置 Webpack 的规律,减少思维负担。 Entry W...
- 下一篇
项目交接杂谈
今天笔者和大家聊一聊在项目交接中遇到的问题 项目交接这种事是不可避免的,一个完整、完善的项目在交接的时候会省不少心,反之就让人抓狂了,尤其是代码交接部分,先不说代码是否写的巧妙,只要命名符合规范,思路清晰,有完善的文档,后续的维护是很轻松的,但是那种想起哪里写哪里,毫无逻辑可言的工程就像一坨屎(虽然笔者写的也自认为是屎),所以接手这种工程,再继续维护就好像:在一坨奇臭无比的一坨屎里面分析、分类、挑选这个人昨天都吃了什么,所以为了尽可能的避免这种狗屎工程,笔者谈一谈在交接的时候交接人需要准备的东西。 以Unity项目为例 所有字段、属性、方法必须有XML注释 命名必须符合命名规范:详见:Unity 之命名规范(一)Unity之命名规范(二) 每一个类有交代设计思路和负责的功能作用,详见:Unity 之命名规范(一)中的评论说明 代码(C#)缩进格式详见:Unity 之命名规范(一)Unity之命名规范(二) 所负责编写系统的整体设计思路文档 所编写系统的资源(程序、美术)替换详细文档(包括字段设置,参数设置,位置注意事项,命名方式等) 随机抽取一套资源进行资源替换测试(按照资源替换文档进...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6