代理设计模式之JDK动态代理CGLIB动态代理原理与源码剖析

代理设计模式

代理模式(Proxy),为其它对象提供一种代理以控制对这个对象的访问。如下图

从上面的类图可以看出,通过代理模式,客户端访问接口时的实例实际上是Proxy对象,Proxy对象持有RealSubject的引用,这样一来Proxy在可以在实际执行RealSubject前后做一些操作,相当于是对RealSubject的Reques方法做了增强。

/**
 * @author kangming.ning
 * @date 2021/5/8 9:51
 */
public class Client {

    public static void main(String[] args) {
        Subject subject = new Proxy();
        subject.request();
    }
}

Proxy类对RealSubject的request方法进行了增强

/**
 * @author kangming.ning
 * @date 2021/5/8 9:49
 */
public class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy() {
        realSubject = new RealSubject();
    }

    @Override
    public void request() {
        System.out.println("proxy do something before");
        realSubject.request();
        System.out.println("proxy do something after");
    }
}

代理设计模式就是这么简单,但却很好用。像上面这种提前设计好的代理可以称为静态代理,因为它是针对某个需要增强的接口直接进行编码设计的。这种方式事实上用的很少,因为它需要针对每个需要增强的接口添加代理类,试想类似于mybatis的Mapper代理如果都是静态代理,是不是会导致我们编写大量代理类,实在麻烦。所以项目中通常都是使用动态代理,所谓动态代理,可以简单理解为代理类可以在运行时动态创建并加载到JVM中,下面做详细介绍。

动态代理

动态代理:从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

其本质和静态代理是一样的,只不过被设计为可以动态生成代理类,使用更加方便,在实际的开发中有大量应用。比如spring的AOP(Aspect Oriented Programming) 切面编程这种唬人的技术,其本质不过就是利用代理模式对方法进行增强。当然spring aop用的是动态代理技术,再具体就是利用JDK动态代理或CGLIB动态代理对针对接口或类生成动态代理类,然后实际执行方法时,实际执行的代理类的逻辑,代理类可以在方法前后做一些操作(增强)。

JDK动态代理

JDK动态代理Proxy是JDK提供的一个用于创建动态代理的一个工具类。下面用一个简单例子说明如何应用JDK动态代理

首先还是需要有被代理的接口,自定股票买卖行为

/**
 *  股票接口
 * @author kangming.ning
 * @date 2024-01-19 16:40
 * @since 1.0
 **/
public interface StockService {

    /**
     * 购买股票接口
     * @param stockName 买的哪个股票
     * @param totalMoney
     */
    void buyStock(String stockName,double totalMoney);

    /**
     * 卖出股票接口
     * @param stockName 卖的哪个股票
     * @param totalMoney
     */
    void sellStock(String stockName,double totalMoney);
}

接口的实现 ,即买卖股票需要做的一些事情。

/**
 * @author kangming.ning
 * @date 2024-01-19 16:54
 * @since 1.0
 **/
public class StockServiceImpl implements StockService {
    @Override
    public void buyStock(String stockName, double totalMoney) {
        System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元");
    }

    @Override
    public void sellStock(String stockName, double totalMoney) {
        System.out.println("成功卖出了股票" + stockName + " 共" + totalMoney + "元");
    }
}

没有代理的情况,买卖这些事情是需要股民自己去做的

/**
 * 没有代理的情况
 *
 * @author kangming.ning
 * @date 2024-01-22 09:50
 * @since 1.0
 **/
public class StockDirectClient {
    public static void main(String[] args) {
        StockService stockService = new StockServiceImpl();
        stockService.buyStock("001", 100);
        stockService.sellStock("002", 200);
    }
}

而有代理的情况,通常表现为,我们买卖的基金,其背后实际是大部分是股票(偏股基金),基金经理可以认为是我们的代理。

/**
 *  韭菜侠(又称为基民)
 * @author kangming.ning
 * @date 2024-01-19 16:57
 * @since 1.0
 **/
public class FragrantFloweredGarlicMan {
    public static void main(String[] args) {
        //韭菜侠发现投资商机,基金好像跌到底部了,果断去抄底
        StockService stockService = new StockServiceImpl();
        StockService proxy = (StockService)Proxy.newProxyInstance(
                //目标类的类加载器
                stockService.getClass().getClassLoader(),
                stockService.getClass().getInterfaces(),
                new StockInvocationHandler(stockService));
        proxy.buyStock("003",100);
        proxy.sellStock("004",200);
    }
}

 StockInvocationHandler如下

/**
 * @author kangming.ning
 * @date 2024-01-19 17:06
 * @since 1.0
 **/
public class StockInvocationHandler implements InvocationHandler {

    /**
     * 代理中的真实对象
     */
    private final Object target;

    public StockInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        //执行被代理对象原方法
        Object invoke = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return invoke;
    }
}

 上面的打印结果类似

before method buyStock
成功购买了股票003 共100.0元
after method buyStock
before method sellStock
成功卖出了股票004 共200.0元
after method sellStock

可以看出,通过动态代理,对原接口调用前后都分别处理了额外的逻辑,和静态代理实现的效果是一致的。只是动态代理不需要事先编辑好相关代理类,而是在执行过程中动态生成代理类,这样一来,接口变动我们也不必修改代理类,所有调整适配工作都在InvocationHandler的实现类里处理。

JDK动态代理原理

 通过上面的案例,我们知道怎么去使用JDK代理了。下面探讨一下其实现原理。Proxy创建动态代理的方法如下

    /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static O

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微软如何打造数字零售力航母系列科普13 - Prime Focus Technologies在NAB 2024上推出CLEAR®对话人工智能联合试点

Prime Focus Technologies在NAB 2024上推出CLEAR对话人工智能联合试点 彻底改变您与内容的互动方式&#xff0c;从内容的创建到分发 洛杉矶&#xff0c;2024年4月9日/PRNewswire/-媒体和娱乐&#xff08;M&E&#xff09;行业人工智能技术解决方案的先驱Prime Focus Techn…

OpenCV 双目三角法计算点云

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 基于三角法计算点坐标的过程类似于我们人类眼睛观察事物的过程: 如上图所示,通过两个相机观察到同一位置,我们可以通过两个相机得到这一位置的投影坐标 ( u r , v r ) , ( u l , v l )

Flutter - Material3适配

demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新&#xff0c;请前往github查看最新代码 Flutter - Material3适配 对比图具体实现一些组件的变化 代码实现Material2的ThemeDataMaterial3的ThemeData Material3适配官方文档 flutter SDK升级到3.16.0之后 …

时间处理基础:Rust 的 chrono 库教程

在开发过程中&#xff0c;我们经常有对时间和日期处理的需求。不论是日历应用、日程安排、还是时间戳记录&#xff0c;准确的时间数据处理都是必不可少的。Rust 社区提供的 chrono 库以其强大的功能和灵活的接口&#xff0c;在 Rust 开发者中广受欢迎。本文将简单介绍 chrono 库…

堆盘子00

题目链接 堆盘子 题目描述 注意点 SetOfStacks应该由多个栈组成&#xff0c;并且在前一个栈填满时新建一个栈 解答思路 将多个栈存储到一个List中&#xff0c;当入栈时&#xff0c;如果List中最后一个栈容量已经达到cap&#xff0c;则需要新建一个栈&#xff0c;将元素推到…

pytorch版本与torchvision版本不匹配问题处理

pytorch版本与torchvision版本不匹配问题处理 问题问题复现解决方法两点注意内容其一&#xff1a;pytorch版本与torchvision版本对应关系其二&#xff1a;CPU版本或GPU版本问题 问题 在新环境中&#xff0c;利用yolov8训练模型的时候报错&#xff0c;错误内容如下&#xff1a;…

反射...

一、反射的定义 二、获取Class对象三种方式 全类名&#xff1a;包名类名。 public class test {public static void main(String [] args) throws ClassNotFoundException {//第一种方式Class class1Class.forName("test02.Student");//第二种方法Class class2Stud…

Nginx配置详细解释:(4)高级配置

目录 1.网页的状态页 2.Nginx第三方模块(echo) 3.变量 4.自定义访问日志 5.Nginx压缩功能 6.https功能 7.自定义图标 Nginx除了一些基本配置外&#xff0c;还有一些高级配置&#xff0c;如网页的状态&#xff0c;第三方模块需要另外安装&#xff0c;支持变量&#xff0c…

宽睿数字平台兼容TDengine 等多种数据库,提供行情解决方案

小T导读&#xff1a;最近&#xff0c;涛思数据与宽睿金融宣布了一项重要合作。在此之前&#xff0c;宽睿金融对 TDengine 进行了性能测试&#xff0c;并根据测试报告的结果&#xff0c;决定将 TDengine 接入宽睿数字平台&#xff0c;以提升高密度行情处理效率。本文将详细介绍此…

idea从git拉取代码需要输入token问题解决

idea使用git 推送代码时&#xff0c;提示token问题&#xff0c;这是因为你的代码仓库是gitlab&#xff0c; 然后打开修改代码后推送时&#xff0c;会默认使用gitlab插件&#xff0c;所以提示输入token解决方式就是把gitlab插件取消使用这样就好了。 取消之后再进行拉取代码即可…

【菜狗学前端】在原生微信小程序使用腾讯地图API接口

一直想调用一下地图API接口什么的&#xff0c;刚好遇到了这个实验就浅浅研究写了一下&#xff0c;顺便总结一下给其他没太了解的人一点便利&#xff0c;希望能够对你有所帮助~ 如何引入、配置、使用、显示。 PS:要是嫌麻烦想要源码/有什么问题欢迎评论/私信&#xff0c;问题的话…

[大模型]Gemma2b-Instruct Lora 微调

本节我们简要介绍如何基于 transformers、peft 等框架&#xff0c;对 Gemma2b 模型进行 Lora 微调。Lora 是一种高效微调方法&#xff0c;深入了解其原理可参见博客&#xff1a;知乎|深入浅出 Lora。 这个教程会在同目录下给大家提供一个 nodebook 文件&#xff0c;来让大家更…

AI智能体的分级

技术的分级 人们往往通过对一个复杂的技术进行分级&#xff0c;明确性能、适用范围和价值&#xff0c;方便比较、选择和管理&#xff0c;提高使用效率&#xff0c;促进资源合理分配和技术改进和标准化。 比如&#xff0c;国际汽车工程师学会&#xff08;SAE&#xff09;定义了自…

诊所管理系统有没有免费的?

随着消费市场的不断变化&#xff0c;消费型医疗领域逐渐受到重视&#xff0c;医疗机构面临激烈的行业竞争。为了提升诊所的经营管理效率和营销能力&#xff0c;选择一款合适的诊所管理系统变得尤为重要。然而&#xff0c;面对市场上众多的系统选择&#xff0c;如何找到适合自己…

Linux 安装ab测试工具

yum -y install httpd-tools ab -help #10个并发连接&#xff0c;100个请求 ab -n 200 -c 100 http://www.baidu.com/

如何制作MapBox个性化地图

我们在《如何在QGIS中加载MapBox图源》一文中&#xff0c;为你分享了在QGIS中加载MapBox的方法。 现在为你分享如何制作MapBox个性化地图的方法&#xff0c;如果你需要最新版本的QGIS及高清图源&#xff0c;请在文末查看获取软件安装包的方法。 新建地图样式 进入Mapbox Stu…

智慧监狱大数据整体解决方案(51页PPT)

方案介绍&#xff1a; 智慧监狱大数据整体解决方案通过集成先进的信息技术和大数据技术&#xff0c;实现对监狱管理的全面升级和智能化改造。该方案不仅提高了监狱管理的安全性和效率&#xff0c;还提升了监狱的智能化水平&#xff0c;为监狱的可持续发展提供了有力支持。 部…

计算机组成原理之计算机的性能指标

目录 计算机的性能指标 复习提示 1.计算机的主要性能指标 1.1机器字长 1.1.1与机器字长位数相同的部件 1.2数据通路带宽 1.3主存容量 1.4运算速度 1.4.1提高系统性能的综合措施 1.4.2时钟脉冲信号和时钟周期的相关概念 1.4.3主频和时钟周期的转换计算 1.4.4IPS的相关…

任务3.8.1 利用RDD实现词频统计

实战&#xff1a;利用RDD实现词频统计 目标 使用Apache Spark的RDD&#xff08;弹性分布式数据集&#xff09;模块实现一个词频统计程序。 环境准备 选择实现方式 确定使用Spark RDD进行词频统计。 Spark版本与Scala版本匹配 选择Spark 3.1.3与Scala 2.12.15以匹配现有Spar…

使用seq2seq架构实现英译法

seq2seq介绍 模型架构&#xff1a; Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种在自然语言处理&#xff08;NLP&#xff09;中广泛应用的架构&#xff0c;其核心思想是将一个序列作为输入&#xff0c;并输出另一个序列。这种模型特别适用于机器翻译、聊天…