浅谈JDK动态代理(上)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

到目前为止,在Java基础进阶这个章节,我们已经帮大家梳理了很多晦涩但极其重要的知识点,包括反射、注解和泛型。这些都是我们迈向中高级程序员的小碎步,我们已经离“成熟的码农”越来越近了,但还不够。今天,我们仍需一起再往前走一小步:JDK动态代理。个人认为Java基础有“四大神兽”,除了刚才说的反射、注解和泛型,JDK动态代理就是最后一道坎。

“动态代理”四个字一出来,估计很多初学者已经开始冒冷汗。它之所以给人感觉很难,有三点原因:

  • 代码形式很诡异,让人搞不清调用逻辑
  • 用到了反射,而很多初学者不了解反射(现在你应该感觉好些了)
  • 包含代理模式的思想,本身比较抽象

尽管动态代理看起来似乎有一定难度,但却必须拿下。因为Spring的事务控制依赖于AOP,AOP底层实现便是动态代理 + 责任链,环环相扣。所以说,搞编程的,拼到到最后还是看基本功,要么是语言基础、要么是计算机基础。

一个小需求:给原有方法添加日志打印

假设你刚进入一个项目组,项目中存在一个Calculator类,代表一个计算器,它可以进行加减乘除操作:

public class Calculator {// 加public int add(int a, int b) {int result = a + b;return result;}// 减public int subtract(int a, int b) {int result = a - b;return result;}// 乘法、除法...
}

现在老大给你提了一个需求:在每个方法执行前后打印日志。

你有什么好的方案?

方案一:直接修改

很多人最直观的想法是直接修改Calculator类:

public class Calculator {// 加public int add(int a, int b) {System.out.println("add方法开始...");int result = a + b;System.out.println("add方法结束...");return result;}// 减public int subtract(int a, int b) {System.out.println("subtract方法开始...");int result = a - b;System.out.println("subtract方法结束...");return result;}// 乘法、除法...
}

上面的方案是有问题的:

  • 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭
  • 如果Calculator类内部有几十个、上百个方法,修改量太大
  • 存在重复代码(都是在核心代码前后打印日志)
  • 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能不做了,于是你又要打开Calculator花十分钟删除日志打印的代码(或回滚分支)!

所以,此种方案PASS!

方案二:静态代理实现日志打印

“静态代理”四个字包含了两个概念:静态、代理。我们先来了解什么叫“代理”,至于何为“静态”,需要和“动态”对比着讲。

代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象。如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求。

常用的代理方式可以粗分为:静态代理动态代理

静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。通过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。

是不是有点晕?是的,我最讨厌这种干巴巴的描述。简而言之,就是这样:

按上面的描述,代理类和目标类需要实现同一个接口,所以我打算这样做:

  • 将Calculator抽取为接口
  • 创建目标类CalculatorImpl实现Calculator
  • 创建代理类CalculatorProxy实现Calculator

抽取接口

/*** Calculator接口*/
public interface Calculator {int add(int a, int b);int subtract(int a, int b);
}

原目标类实现接口

/*** 目标类,实现Calculator接口(如果一开始就面向接口编程,其实是不存在这一步的,CalculatorImpl原本就实现Calculator接口)*/
public class CalculatorImpl implements Calculator {// 加public int add(int a, int b) {int result = a + b;return result;}// 减public int subtract(int a, int b) {int result = a - b;return result;}// 乘法、除法...
}

新增代理类并实现接口

/*** 静态代理类,实现Calculator接口*/
public class CalculatorProxy implements Calculator {// 代理对象内部维护一个目标对象引用private Calculator target;// 通过构造方法,传入目标对象public CalculatorProxy(Calculator target) {this.target = target;}// 调用目标对象的add,并在前后打印日志@Overridepublic int add(int a, int b) {System.out.println("add方法开始...");int result = target.add(a, b);System.out.println("add方法结束...");return result;}// 调用目标对象的subtract,并在前后打印日志@Overridepublic int subtract(int a, int b) {System.out.println("subtract方法开始...");int result = target.subtract(a, b);System.out.println("subtract方法结束...");return result;}// 乘法、除法...
}

测试案例

使用代理对象完成加减乘除,并且打印日志:

public class Test {public static void main(String[] args) {// 把目标对象通过构造器塞入代理对象Calculator calculator = new CalculatorProxy(new CalculatorImpl());// 代理对象调用目标对象方法完成计算,并在前后打印日志calculator.add(1, 2);calculator.subtract(2, 1);}
}  

静态代理的优点:可以在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了上一种方案4大缺点中的第1、4两点:

  • 直接修改源程序,不符合开闭原则,即好的程序设计应该对扩展开放,对修改关闭(✅,如果一开始就面向接口编程,这一步其实是不需要的)
  • 如果Calculator类内部有几十个、上百个方法,修改量太大(❎,目标类有多少个方法,代理类就要重写多少个方法)
  • 存在重复代码(都是在核心代码前后打印日志)(❎,代理类中的日志代码是重复的)
  • 日志打印硬编码在代理类中,不利于后期维护:比如你花了一上午终于写完了,组长告诉你这个功能不做了(✅,别用代理类就好了)

静态代理的问题

上面的代码中,为了给目标类做日志增强,我们编写了代理类,而且准备了一个构造器接收目标对象。代理代理对象构造器的参数类型是Calculator,这意味着它只能接受Calculator的实现类对象,亦即我们写的代理类CalculatorProxy只能给Calculator做代理,它们绑定死了!

如果现在我们系统需要全面改造,要给其他类也添加日志打印功能,就得为其他几百个接口都各自写一份代理类...

自己手动写一个类并实现接口实在太麻烦了。仔细一想,我们其实想要的并不是代理类,而是代理对象!

你细品上面加粗的这句话,是不是好像一句废话?没有类哪来的对象?!

其实我的意思是,能否让JVM根据接口自动生成代理对象呢?

比如,有没有一个方法,我传入接口+增强的代码(比如打印日志),它就给我自动返回代理对象呢?这样就能省去编写代理类这个无用的“中介”了,没有中间商赚差价,岂不爽哉?

JDK,能做到吗?

预知后事如何,请听下回分解~

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/162163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣OJ题讲解——循环队列

今天我们一起来做一道关于队列的OJ题目,这是力扣题目622题,点击题目链接可以直接跳转,https://leetcode.cn/problems/design-circular-queue/ 首先,我们看到要求,需要我们实现哪些功能? 我们需要设置队列长…

经典双指针算法试题(二)

📘北尘_:个人主页 🌎个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 文章目录 一、有效三角形的个数1、题目讲解2、讲解算法原理3、代码实现 二、查找总价格为目标值的两个商…

Hutool

一、简介 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅 官方文档: https://www.hutool.cn/docs/#/ 二、包含组件 一个Java基础工具类,对文…

allegro画封装时使用坐标指令无效

使用坐标指令时显示:“Pick is outside the extent of the drawing…pick again” 这是因为你放的引脚已经超出你这个绘制界面的定义尺寸,需要到Setup->Design pararmeters…里面去将图幅改大一点,如下图所示: 然后点击Design…

消息中间件——RabbitMQ(三)理解RabbitMQ核心概念和AMQP协议!

前言 本章学习,我们可以了解到以下知识点: 互联网大厂为什么选择RabbitMQ?RabbiMQ的高性能之道是如何做到的?什么是AMQP高级协议?AMQP核心概念是什么?RabbitMQ整体架构模型是什么样子的?Rabbi…

P8599 [蓝桥杯 2013 省 B] 带分数(dfs+全排列+断点判断)

思路&#xff1a;1.深度枚举所有排列情况 2.设置为每个排列设置两个断点&#xff0c;分为三部分&#xff1a;a,b,c 3.转换为乘法判断条件&#xff0c;满足加一 代码如下&#xff1a;&#xff08;可用next_permutation全排列函数代替dfs&#xff09; #include<iostream>…

全面的日志监控管理工具

企业网络由众多日志源组成。集中监控这些日志源有助于防止数据威胁和网络攻击&#xff0c;综合日志监控解决方案可以自动执行日志管理流程&#xff0c;通过关联日志来识别恶意活动&#xff0c;并帮助满足IT合规性要求。 不同类型的日志监控 EventLog Analyzer 综合日志监控解…

智慧法院档案数字化解决方案

智慧法院档案数字化解决方案可以采用以下步骤&#xff1a; 1. 确定数字化目标&#xff1a;明确数字化的目标和范围&#xff0c;比如将所有的案件相关文件、纸质档案和材料进行数字化。 2. 确定数字化流程&#xff1a;制定数字化的流程和标准&#xff0c;比如采用哪些设备和软件…

【C语言】qsort函数

目录 简介 头文件 ​编辑 函数原型&#xff1a; 参数函数如何写&#xff1a; 参数函数要求&#xff1a; qsort对整性数据的排序&#xff1a; qsort对字符型数据的排序&#xff1a; 对结构体类型的内部元素排序&#xff1a; 函数的底层是以快速排序实现的 但是本文不深入…

小黑子—Maven高级

Maven高级篇 二 小黑子的Maven高级篇学习1. 分模块开发1.1 分模块开发设计1.2 分模块开发实现1.2.1 抽取domain层1.2.2 抽取dao层 2. 依赖管理2.1 依赖传递2.2 可选依赖2.3 排除依赖 3. 继承与聚合3.1 聚合3.2 继承3.3 总结 4. 属性4.1 配置文件加载属性4.2 版本管理 5. 多环境…

【开源】基于Vue.js的民宿预定管理系统

项目编号&#xff1a; S 058 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S058&#xff0c;文末获取源码。} 项目编号&#xff1a;S058&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色…

梦开始的地方——Adobe Premiere Pro

今天&#xff0c;我们来说说一款老生常谈的相信也是很多人都经常迫切需要的软件。Adobe Premiere Pro&#xff0c;简称Pr&#xff0c;是由Adobe公司开发的一款视频编辑软件。 Premiere Pro是视频编辑爱好者和专业人士必不可少的视频编辑工具。它可以提升您的创作能力和创作自由…

httpd(Web服务器)

名词解释 1、URL&#xff1a;Uniform Resource Locator&#xff0c;统⼀资源定位符 2、⽹址格式&#xff1a;<协议>://<主机或主机名>[:port]/<⽬录资源,路径> 3、主机地址/主机名&#xff1a;主机地址是服务器在因特⽹所在的IP地址。主机名就需要域名解析…

装饰器设计模式是什么?什么是 Decorator 装饰器设计模式?Python 装饰器设计模式示例代码

什么是 Decorator 装饰器设计模式&#xff1f; 装饰器模式是一种结构型设计模式&#xff0c;它允许向现有对象动态地添加新功能&#xff0c;同时不改变其结构。这种模式实现了对对象的包装&#xff0c;称为装饰器&#xff0c;并且可以在运行时动态地添加、修改或删除对象的行为…

重磅!这本30w人都在看的Python数据分析畅销书:更新了!

想学习python进行数据分析&#xff0c;这本《利用python进行数据分析》是绕不开的一本书。目前该书根据Python3.10已经更新到第三版。 Python 语言极具吸引力。自从 1991 年诞生以来&#xff0c;Python 如今已经成为最受欢迎的解释型编程语言。 pandas 诞生于2008年。它是由韦…

NX二次开发UF_CAM_set_clear_plane_data 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CAM_set_clear_plane_data Defined in: uf_cam_planes.h int UF_CAM_set_clear_plane_data(tag_t object_tag, double origin [ 3 ] , double normal [ 3 ] ) overview 概述 De…

快慢指针判断环形链表

我们在前面文章中写过用快慢指针判断链表是否带环&#xff1a; leetcode&#xff1a;环形链表-CSDN博客 我们用的是slow指针一次走一步&#xff0c;fast指针一次走两步&#xff0c;当slow入环后开始了追击&#xff0c;每走一次距离缩短1&#xff0c;最终就会相遇 思考问题 …

【LeetCode】每日一题 2023_11_23 HTML 实体解析器(调库/打工)

文章目录 刷题前唠嗑题目&#xff1a;HTML 实体解析器题目描述代码与解题思路 结语 刷题前唠嗑 题目&#xff1a;HTML 实体解析器 题目链接&#xff1a;1410. HTML 实体解析器 题目描述 代码与解题思路 func entityParser(s string) (ans string) {return strings.NewRepla…

NX二次开发UF_CAM_set_lower_limit_plane_tag 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CAM_set_lower_limit_plane_tag Defined in: uf_cam_planes.h int UF_CAM_set_lower_limit_plane_tag(tag_t object_tag, tag_t target_tag ) overview 概述 Set the tag of a …

使用 PowerShell 创建共享目录

在 Windows 中&#xff0c;可以使用共享目录来将文件和文件夹共享给其他用户或计算机。共享目录可以通过网络访问&#xff0c;这使得它们非常适合用于文件共享、协作和远程访问。 要使用 PowerShell 创建共享目录&#xff0c;可以使用 New-SmbShare cmdlet。New-SmbShare cmdl…