JAVA实现动态代理的两种方式及主要的区别

两种动态代理

JAVA中实现动态代理主要目的是为了实现AOP,即面向切面编程。
而动态代理主要是在程序运行期间,基于原类生成代理类,并且将需要织入的代码加入到代理类的方法中,可以实现动态的代码链接。
JAVA实现动态代理的两种方式分别为:
JDK代理
CGLIB代理

基于代码分析

结合着代码,我们进行两种动态代理方式的分析
JDK代理

package proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 基于JDK实现接口的代理* 基于InvocationHandler类和Proxy类进行JVM代理实现*/
public class JVMProxy {//被代理对象private Object target;//param: 被代理对象实例//return: 代理对象实例public Object getProxy(Object target){this.target = target;ClassLoader classLoader = target.getClass().getClassLoader();Class[] interfaces = target.getClass().getInterfaces();InvocationHandler h = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//被代理方法织入代码System.out.println("JDK代理方法执行前织入逻辑"+method.getName());//调用被代理对象target,执行对应方法的原始逻辑Object o = method.invoke(target,args);//被代理方法后执行代码System.out.println("JDK代理方法执行后织入逻辑"+method.getName());return o;}};return Proxy.newProxyInstance(classLoader,interfaces,h);}
}

JVM代理基于InvocationHandler和Proxy类实现。每块重要代码的逻辑都在上面的示例中标明了;
GCLIB代理

package proxy;import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** 基于CGLIB实现动态代理* 使用Enhance类实现代理类的构建,基于intercept()方法捕获调用被代理对象的对应方法,随后调用代理对象增强后的方法,并且通过invokeSuper()实现原方法的调用*/
public class CglibProxy implements MethodInterceptor {//业务类对象,供代理方法中进行真正的业务方法调用private Object target;public Object getCglibProxy(Object target){//为业务对象赋值this.target = target;//创建加强器,构造动态代理对象Enhancer enhancer = new Enhancer();//给动态代理对象设置被代理类enhancer.setSuperclass(this.target.getClass());//设置回调,对代理类所有方法的调用,都会调用CallBack,而CallBack则需要intercept()方法进行拦截enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("before execute method " + method.getName());//这里调用的是代码方法的invokeSuper()方法,而非invoke方法;和使用JVM实现代码有区别//invokeSuper()调用的是被代理类的被调用的方法,是原本的方法;而invoke()调用的是代理类的本方法,会一直被intercept捕获,然后循环执行invoke;Object result = methodProxy.invokeSuper(o,objects);System.out.println("after execute method " + method.getName());return result;}
}

其中比较重要的就是Enhance类还有intercept方法,代理类的方法调用都会调用回调函数:CallBack方法,然后被intercept方法捕获,之后执行织入代码的逻辑;

两者的差别

面试官如果问到这个问题?你该怎么回答呢?
按照一些教程的说法,
两者的主要差别是:JDK动态代理只能代理接口,而CGLIB动态代理可以代理接口、普通的JavaBean。
但是其中的根因是什么?这个问题如果不加以探究,就会知其然,不知其所以然,如果面试官问到,我们也会支支吾吾,说不太清楚。
所以其中根因,我探究之后发现是因为:
JDK动态代理只在外部调用时生效,在通过this.method的调用时会失效。这也是@Transactional注解失效的场景之一,即类内部调用时,不会走JVM代理。这个原因我认为在于类间的调用,是通过Spring框架控制的,所以会走代理。
而CGLIB无论是内部调用还是外部调用,代理都会生效。
我们通过下面的代码验证一下

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import proxy.CglibProxy;
import proxy.JVMProxy;
import service.IJdkProxyService;
import service.Impl.AopDemoServiceImpl;public class Application {public static void main(String[] args) {CglibProxy cglibProxy = new CglibProxy();IJdkProxyService a = (IJdkProxyService) cglibProxy.getCglibProxy(new AopDemoServiceImpl());//代理类默认会将被代理类的所有方法都进行代理,比如hashCode()和toString()方法a.toString();JVMProxy jvmProxy = new JVMProxy();IJdkProxyService b = (IJdkProxyService) jvmProxy.getProxy(new AopDemoServiceImpl());b.toString();}
}

执行的结果如下

CGLIB代理方法执行前织入逻辑 toString
CGLIB代理方法执行前织入逻辑 hashCode
CGLIB代理方法执行后织入逻辑 hashCode
CGLIB代理方法执行后织入逻辑 toString
JVM代理方法执行前织入逻辑toString
JVM代理方法执行后织入逻辑toString

有没有发现,执行结果中,CGLIB代理多打印了一个hashCode方法的记录,但是我们并没有调用hashCode方法;所以我们可以猜测是toString方法内部又调用了hashCode方法。
我们看下Object.toString()的源码,是否和我们猜测的一致?

    public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}

我们发现果然如此,toString内部调用了hashCode方法,这次调用被CGLIB代理捕捉并拦截了,但没有被JVM代理捕捉到(实际上是没有调用JVM代理方法),导致两种代理的执行结果不一致。
所以分析到这里,我们就可以确定两种代理的主要区别了。而这种区别的主要原因,是两种代理底层实现不同,CGLIB中是对被代理对象方法的调用都会调用代理方法的回调函数,被intercept捕捉到之后就会调用织入的代码;这点和JVM代理不同。

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

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

相关文章

如何通过 WordPress 数据库启用插件?【进不去后台可用】

如果您无法访问 WordPress 后台并需要激活插件以恢复访问权限,则可以通过 WordPress 数据库来实现。本文将向您展示如何使用数据库轻松激活 WordPress 插件。 何时使用数据库激活 WordPress 插件? 许多常见的 WordPress 错误会阻止网站所有者访问 WordP…

k8s目录

k8s笔记目录,更新中... 一 概念篇 1.1概念介绍 1.2 pod 1.3 controller 1.3.1 deployment 1.3.2 statefulset 1.3.3 daemonset 1.3.4 job和cronJob1 1.4 serivce和ingress 1.5 配置与存储 1.5.1 configMap 1.5.2 secret 1.5.3 持久化存储 1.5.4 pv、…

selenium 和 chromedriver 使用的一些总结

1 selenium 下载地址 selenium PyPIhttps://pypi.org/project/selenium/ 2 chromedriver 下载地址 ,可以下载最新版的 chromedriver ChromeDriver - WebDriver for Chrome - Downloadshttps://chromedriver.chromium.org/downloadsChrome for Testing availabi…

管理类联考——写作——论说文——实战篇——行文篇——通用性强,解释多种现象的经典理论——谈必要

前言 本节内容涉及“社会分工理论”“资源稀缺性”“瓶颈理论”等理论。这些理论一般用在“利大于弊式结构”中“整体有必要”的部分,也可用于“AB二元类”题目“谈好处”的部分。 需要注意的是,“有好处”一般指有它更好;“有必要”一般指没…

Kafka介绍

目录 1,kafka简单介绍 2,kafka使用场景 3,kafka基本概念 kafka集群 数据冗余 分区的写入 读取分区数据 顺序消费 顺序消费典型的应用场景: 批量消费 提交策略 kafka如何保证高并发 零拷贝技术(netty&#…

MySQL迁移到PostgreSQL操作指南

将MySQL数据库迁移到PostgreSQL数据库需要一些步骤和注意事项。以下是一个简单的指南,帮助您进行这样的迁移: 数据迁移: 首先,您需要将MySQL数据库中的数据迁移到PostgreSQL。有几种方法可以实现这一点: 使用工具&…

MIAOYUN获评“2023年度一云多芯稳定安全运行优秀案例”

2023年7月25日至26日,由中国信息通信研究院(简称“中国信通院”)、中国通信标准化协会主办的以“云领创新,算启新篇”为主题的“2023可信云大会”在北京成功举办。会上公布了多项前瞻领域的评估结果和2023年度最佳实践案例&#x…

IOS看书最终选择|源阅读转换|开源阅读|IOS自签

环境:IOS想使用 换源阅读 问题:换新手机,源阅读下架后,没有好的APP阅读小说 解决办法:自签APP 转换源仓库书源 最终预览 :https://rc.real9.cn/ 背景:自从我换了新iPhone手机,就无法…

zjzcyList.stream().map(Pb_zjzcy::getZjid).collect(Collectors.toList()); 解释一下

zjzcyList.stream().map(Pb_zjzcy::getZjid).collect(Collectors.toList()); 解释一下 这段代码是使用Java 8的流式处理(Stream)对一个存储了对象的列表(zjzcyList)进行操作,并最终返回一个包含了列表中每个对象的Zji…

svn 命令

连接服务器 ssh usernameip (不用端口)checkout 项目 svn checkout repourl --username username --password passwordrepourl: svn url删除文件(移除版本控制)1.从 SVN 移除版本控制,并删除文件svn delete filename2.从 SVN 移除版本控制,但是不删除文件…

生鲜蔬果小程序的完整教程

随着互联网的发展,线上商城成为了人们购物的重要渠道。其中,小程序商城在近年来的发展中,备受关注和青睐。本文将介绍如何使用乔拓云网后台搭建生鲜果蔬配送小程序,并快速上线。 首先,登录乔拓云网后台,进入…

vue2-常用的修饰符有哪些应用场景?

1、修饰符是什么? 在程序的世界里,修饰符是用于限定类型以及类型成员的声明的一种 符号。 在vue中,修饰符处理了许多DOM事件的细节,让我们不再需要花大量的时间去处理这些重复的事情,而能有更多的精力专注于程序的逻辑…

3.PyCharm安装

PyCharm是由JetBrains推出的Python开发IDE,是最受欢迎的Python IDE之一。PyCharm为Python开发者提供了许多高级功能如代码自动完成、调试等。它使用智能引擎来分析代码,能够自动识别代码中的错误并提供快速修复方案。PyCharm适用于各种规模的项目,包括小型Python脚本和大型P…

K8S系列文章之 自动化运维利器 Fabric

Fabric 主要用在应用部署与系统管理等任务的自动化,简单轻量级,提供有丰富的 SSH 扩展接口。在 Fabric 1.x 版本中,它混杂了本地及远程两类功能;但自 Fabric 2.x 版本起,它分离出了独立的 Invoke 库,来处理…

计算机网络(3) --- 网络套接字TCP

计算机网络(2) --- 网络套接字UDP_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131977544?spm1001.2014.3001.5501 目录 1.TCP 1.服务端接口介绍 1.listen状态 2.accept获取链接 2.客户端接口介绍 2.TCP的服务器…

List与Set的区别

List与Set的区别 大家好,在我们平时的代码编写过程中,经常会碰到需要使用到集合类型: List与Set。很多时候,我们可能会将它们视为同一种类型进行使用,但是在实际的编程逻辑中,它们之间是存在很大差别的。接下来我们就…

召唤神龙打造自己的ChatGPT

在之前的两篇文章中,我介绍了GPT 1和2的模型,并分别用Tensorflow和Pytorch来实现了模型的训练。具体可以见以下文章链接: 1. 基于Tensorflow来重现GPT v1模型_gzroy的博客-CSDN博客 2. 花费7元训练自己的GPT 2模型_gzroy的博客-CSDN博客 有…

机器人状态估计:robot_localization 功能包使用方法

机器人状态估计:robot_localization 功能包基本使用 前言功能包简介基本使用数据输入与数据输出坐标系设置性能参数调试 前言 移动机器人的状态估计需要用到很多传感器,因为对单一的传感器来讲,都存在各自的优缺点,所以需要一种多…

简单工厂模式(Simple Factory)

简单工厂模式,又称为静态工厂方法(Static Factory Method)模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单工厂模式不属于GoF的23个…

AutoSAR系列讲解(实践篇)12.1-Diagnostics简介

目录 前言 一、UDS协议 1、Service Identifier(SID) 2、协议规范 3、举个例子 二、Aut