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

2024-06-11 22:04

本文主要是介绍代理设计模式之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();}@Overridepublic 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 {@Overridepublic void buyStock(String stockName, double totalMoney) {System.out.println("成功购买了股票" + stockName + " 共" + totalMoney + "元");}@Overridepublic 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;}@Overridepublic 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}*/@CallerSensitivepublic static O

这篇关于代理设计模式之JDK动态代理CGLIB动态代理原理与源码剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1052384

相关文章

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

JDK多版本共存并自由切换的操作指南(本文为JDK8和JDK17)

《JDK多版本共存并自由切换的操作指南(本文为JDK8和JDK17)》本文介绍了如何在Windows系统上配置多版本JDK(以JDK8和JDK17为例),并通过图文结合的方式给大家讲解了详细步骤,具有... 目录第一步 下载安装JDK第二步 配置环境变量第三步 切换JDK版本并验证可能遇到的问题前提:公司常

Spring 中 BeanFactoryPostProcessor 的作用和示例源码分析

《Spring中BeanFactoryPostProcessor的作用和示例源码分析》Spring的BeanFactoryPostProcessor是容器初始化的扩展接口,允许在Bean实例化前... 目录一、概览1. 核心定位2. 核心功能详解3. 关键特性二、Spring 内置的 BeanFactory

mybatis-plus 实现查询表名动态修改的示例代码

《mybatis-plus实现查询表名动态修改的示例代码》通过MyBatis-Plus实现表名的动态替换,根据配置或入参选择不同的表,本文主要介绍了mybatis-plus实现查询表名动态修改的示... 目录实现数据库初始化依赖包配置读取类设置 myBATis-plus 插件测试通过 mybatis-plu

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想

kotlin中的模块化结构组件及工作原理

《kotlin中的模块化结构组件及工作原理》本文介绍了Kotlin中模块化结构组件,包括ViewModel、LiveData、Room和Navigation的工作原理和基础使用,本文通过实例代码给大家... 目录ViewModel 工作原理LiveData 工作原理Room 工作原理Navigation 工