Spring5应用之JDK动态代理

news2024/11/27 20:32:03

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Spring5应用专栏_Aomsir的博客-CSDN博客

文章目录

  • 前言
  • JDK动态代理
    • 开发步骤
    • 方法原型分析
      • ClassLoader
      • Class<?>[]
      • InvocationHandler
  • 总结
  • 参考文献

前言

在我们之前的探索中,已经详细解读了AOP如何借助动态字节码技术来构建动态代理类。实际上,实现动态代理的方式不止一种。其中,JDK动态代理CglibASMJavassist都是业界常用的技术手段。今天,我将引导大家深入 Spring AOP的底层原理,揭示其背后所采用的动态代理技术是如何工作的。为了更加系统地呈现这一内容,我特地选取了JDK动态代理与Cglib这两大主流方法,进行详实的解读。首先,我们将着重了解JDK动态代理的核心原理和实际应用情境。我的目标是,希望大家在了解这些深入的分析后,能够更为全面和深入地理解动态代理背后的精妙设计和实现

JDK动态代理

开发步骤

在之前关于AOP动态代理的探讨中,我们了解到创建AOP代理涉及三大关键要素:原始对象额外功能,以及一个代理对象该代理对象与原始对象共同实现相同的接口。这种结构在更为底层JDK动态代理的开发中也得到了体现。

考虑以下示例:其中,UserService代表原始对象实现的接口;UserServiceImpl是具体的原始对象,而通过InvocationHandler我们为其定义了额外功能。最终,我们利用Proxy.newProxyInstance方法成功地创建了代理类对象。

当我们运行这段代码,测试的输出结果与我们预期的完全一致,证明了我们成功地为原始对象UserServiceImpl注入了额外功能,而这一切都得益于JDK提供的动态代理机制
在这里插入图片描述

public class TestJDKProxy {

    private static final Logger log = LoggerFactory.getLogger(TestJDKProxy.class);

    @Test
    public void test1() {
        // 创建原始对象
        UserService userService = new UserServiceImpl();

        // 创建JDK动态代理对象
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        log.debug("log before");

                        // 执行原始方法(涉及反射代码),获取其返回值
                        Object ret = method.invoke(userService, args);

                        log.debug("log after");
                        return ret;
                    }
                });

        userServiceProxy.login("admin", "123456");
    }
}

方法原型分析

从上述示例中,我们可以清晰地看到JDK动态代理对象的创建核心——Proxy.newProxyInstance()方法。这个方法接收三个关键参数:ClassLoaderClass<?>[]InvocationHandler。它们分别代表了类加载器原始类实现的接口的Class对象,以及用于注入的额外功能

接下来我将带着深入了解这三个参数的工作原理和用途,这对于我们完整地理解JDK动态代理机制是至关重要的。希望通过这次详尽的分析,大家能够对JDK动态代理有一个更为深入和全面的认识
在这里插入图片描述

ClassLoader

ClassLoader,即类加载器,在Java中起着至关重要的作用。但要深入了解它,首先必须回顾Java程序的标准运行流程。典型情况下,当程序启动时,类加载器首先会读取类对应的字节码文件(.class文件),将其加载到JVM中。随后,JVM会基于这些字节码数据,通过类加载器创建出对应的Class对象,并根据需要进一步实例化为具体对象

这个流程在遇到动态代理时遭遇了挑战。动态代理,顾名思义,其类是在运行时动态生成的,它并没有预先准备好的.class文件。那么,如何为这样的代理类创建一个Class对象呢?又或者说,ClassLoader在这里扮演什么角色?

实际上,当我们请求JVM创建一个动态代理时,JVM会为我们“临时”生成这个代理类的字节码。这并不是从文件系统中读取的,而是基于我们给定的接口和实现,即时生成的。 在这里,ClassLoader的任务是加载这个“临时生成”的字节码到JVM的内存中。这意味着,尽管代理类的字节码并没有物理存在,但ClassLoader依然可以处理它,就像处理其他常规Java类一样。

但这里有一个细节值得注意:这个用于加载动态代理的ClassLoader并不是新创建的,而是借用了现有的一个类加载器。这点尤为重要,因为在Java项目中,每个类都有它自己对应的类加载器,确保了类的隔离和安全性。在动态代理的场景中,我们实际上是复用了某个现有类的加载器来加载代理类,确保代理类能够顺利地与原始 类在同一个上下文中工作

Class<?>[]

Proxy.newProxyInstance()方法中,第二个参数Class<?>[]起着至关重要的作用。它是一个Class对象的数组,代表了一组接口。当我们希望创建一个动态代理对象时,这些接口定义了创建的代理对象将额外功能加在哪些原始类方法上

为什么是接口而不是具体的类呢?这是因为JDK的动态代理机制建立在接口的基础之上。具体来说,动态代理生成的代理类会实现指定的一组接口,而不是继承某个类。这使得动态代理具有很大的灵活性,因为一个Java类可以实现多个接口,但只能继承一个父类

通过传递一个Class对象的数组作为参数,我们告诉JVM我们希望代理类实现哪些接口,将额外功能加在哪些原始类方法上。然后,动态生成的代理类将会实现这些接口,并在每个接口方法的实现中,根据我们的需求,调用InvocationHandler来处理方法调用。

简而言之,Class<?>[]参数为Proxy.newProxyInstance()方法提供了一个蓝图,说明代理类应如何构建,并且定义了其行为特征

InvocationHandler

InvocationHandler是JDK动态代理机制中的一个关键接口,其定义了如何在代理对象上处理方法调用。该接口中,仅包含一个名为invoke的方法。此方法在设计上,旨在调用原始对象的方法,同时为其注入额外的功能

当代理对象上的一个方法被调用时,invoke方法就会被触发。它提供了我们一个场所,允许我们在原始方法执行前后添加自定义的行为或功能,从而扩展或改变原始方法的行为。
关于invoke方法的三个参数,它们分别为:

  1. Proxy:这是正在调用的方法所属的代理实例。大多数情况下,我们并不直接使用它,因为在InvocationHandler实现内部调用该代理对象会导致无限递归。
  2. Method:这代表了被代理对象的某个具体方法的反射对象它为我们提供了调用原始对象方法的能力,这可以通过method.invoke(targetObject, args)来完成,其中targetObject是原始对象的实例
  3. Object[]:这是被代理方法的参数数组,表示在代理对象上调用方法时传递的参数。

通过组合上述三个参数,我们可以在invoke方法中灵活地调用原始方法,同时根据需要为其添加额外的逻辑或功能,从而实现对原始方法行为的定制。

在我们深入了解JDK的InvocationHandler接口后,不禁让人回想起Spring AOP中的一个相似结构——MethodInterceptor接口。Spring AOP在动态代理实现中提供了这个接口,它与JDK的动态代理机制的核心思想相似,但是Spring对其进行了封装。

MethodInterceptor接口的设计是简洁而聚焦的。它的中心是一个invoke方法,这个方法的目的与InvocationHandler中的invoke相似: 拦截并增强方法调用。但不同的是,Spring选择了一个集成的方法来传递信息。而不是分开的多个参数,MethodInterceptor的invoke方法接受一个封装了方法调用详情的MethodInvocation对象。这个对象包含了调用的方法、目标对象、参数等所有必要的信息,而且还提供了一个proceed方法,用于执行原始的方法调用。
在这里插入图片描述

这种设计方式优雅地集合了所有的方法调用信息,使得代理的实现既简洁又集中。开发者不再需要从多个参数中提取信息,而是可以直接与MethodInvocation对象交互,从而更直观、高效地实现代理逻辑。

总结来说,Spring的MethodInterceptor接口提供了一种高效、简化的方式来实现动态代理,使得开发者可以更聚焦于业务逻辑的增强,而不是方法调用的底层细节

package java.lang.reflect;
public interface InvocationHandler {
    
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
package org.aopalliance.intercept;

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation var1) throws Throwable;
}

总结

今天我们详细探讨了Spring动态代理开发中的JDK动态代理机制。这个机制在Java的核心库中有着深厚的根基,而Spring则充分利用了它,为我们带来了灵活且强大的AOP实现。通过对JDK动态代理的深入学习,我们不仅加深了对Java代理工作原理的理解,也为接下来的学习打下了坚实的基础。在接下来的文章中,我们将转向另一个同样重要但工作机制截然不同的动态代理技术——Cglib。希望大家能继续保持热情,与我一起进一步探索Spring AOP背后的精妙技术

参考文献

  • 孙哥孙帅suns说Spring5~学不会Spring? 因为你没找对人
  • Spring官方文档

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

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

相关文章

计算机竞赛 深度学习猫狗分类 - python opencv cnn

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习猫狗分类 ** 该项目较为新颖&a…

[C++随笔录] stack queue模拟实现

stack && queue模拟实现 stack的实现stack测试用例queue的实现queue测试用例deque stack的实现 &#x1f5e8;️stack的容器适配器应该选什么比较好呢? 首先, stack的特点是 头部入, 尾部出 ⇒ 尾插 和 尾删操作比较频繁 我们前面学过的容器有 vector 和 list, vecto…

代理服务器拒绝连接

在使用电脑时&#xff0c;有些时候会出现 代服务器拒绝连接 的提示&#xff0c;在这个时候通常采用的一种解决方案是&#xff1a; 首先 点击 winR &#xff0c; 后在其中输入 inetcpl.cpl&#xff0c;点击 连接->局域网设置&#xff0c;把选择框全部清空&#xff0c;点击确…

QT:绘图

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> //绘图事件class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent 0);~Widget();void paintEvent(QPaintEvent *event); //重写绘图事件void timerEve…

Claude一个比chat-gpt相同但使用门槛更低的ai生产力

本篇文章主要介绍Claude的官网、使用方法&#xff0c;以及Claude 的特性和与chat-gpt的区别。 日期&#xff1a;2023年6月17日 作者&#xff1a;任聪聪 Claude 的介绍及相关信息 Claude 也是一个与chat-gpt等同的nlp大语言模型&#xff0c;效果和gpt几乎差不多&#xff0c;能够…

程序在线报刊第一期

文章目录 程序在线报刊第一期排序算法&#xff1a;优化数据处理效率的核心技术回顾区块链技术&#xff1a;去中心化引领数字经济新时代展望AI未来&#xff1a;智能化时代的无限可能 程序在线报刊第一期 排序算法&#xff1a;优化数据处理效率的核心技术 近年来&#xff0c;随…

区块链(9):java区块链项目的Web服务实现之实现web服务

1 引入pom依赖 <dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.8.v20171121</version></dependency><dependency><groupId>org.eclipse.jetty</groupId…

Tomcat多实例、负载均衡、动静分离

Tomcat多实例部署 安装jdk [rootlocalhost ~]#systemctl stop firewalld.service [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#cd /opt [rootlocalhost opt]#ls apache-tomcat-8.5.16.tar.gz jdk-8u91-linux-x64.tar.gz rh [rootlocalhost opt]#tar xf jdk-8u91-linu…

Vivado与Notepad++关联步骤

填写内容 先看"关联步骤"再看此处&#xff1a; 在“editor”栏中填写 Notepad的路径&#xff0c;并加上[file name] -n[line number]&#xff0c; 这里我的 Notepad 的路径为 C:/Program Files (x86)/Notepad/notepad.exe &#xff1b; 故这里我就填上以下内容即可…

【人工智能 | 认知观与系统类别】从宏观角度看人工智能认知观与系统类别:探索人工智能无垠领域

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

Spirng Cloud Alibaba Nacos注册中心的使用 (环境隔离、服务分级存储模型、权重配置、临时实例与持久实例)

文章目录 一、环境隔离1. Namespace&#xff08;命名空间&#xff09;&#xff1a;2. Group&#xff08;分组&#xff09;&#xff1a;3. Services&#xff08;服务&#xff09;&#xff1a;4. DataId&#xff08;数据ID&#xff09;&#xff1a;5. 实战演示&#xff1a;5.1 默…

SpringBoot终极讲义第二章笔记

01.关于Import 和 ImportResource Import注解用法(类上): 一般和Configuration一起使用,用来导入里面Bean方法返回的对象 ImportResource(类上):一般和Configuration一起使用,用来导入某个.XML文件里的bean 个人觉得这两个注解有点鸡肋 SpringBoot启动类默认扫描的是启动类…

电子地图 | VINS-FUSION | 小觅相机D系列

目录 一、相关介绍 二、VINS-FUSION环境安装及使用 &#xff08;一&#xff09;Ubuntu18.04安装配置 1、Ubuntu下载安装 2、设置虚拟内存&#xff08;可选&#xff09; &#xff08;二&#xff09;VINS-FUSION环境配置 1、ros安装 2、ceres-solver安装 3、vins-fusion…

智慧公厕与传统公共厕所对比五大优势是什么?

随着科技的不断发展&#xff0c;智慧公厕成为城市建设的新亮点。与传统公共厕所相比&#xff0c;它具备许多独特优势和巨大的价值。本文将以智慧公厕领先厂家广州中期科技有限公司&#xff0c;大量精品案例项目实景实例实图&#xff0c;深入探讨智慧公厕的各个方面的特点&#…

PHP禁止单个用户多设备同时登陆,限制单个用户在多端重复登录

逻辑简单,主要是3点&#xff1a; 1.登录的时候写入一个最新的登录IP到user表其中一个last_login_ip字段 2.登录成功的时候,转入到index控制器或者index方法之前先进行查询&#xff1a; 1).当前IP 2).数据库字段当前用户存储的last_login_ip里面的IP 3.然后进行判断&#xff0…

前端面试题:在浏览器输入url回车后发生什么?浏览器渲染页面详细流程

❤️在浏览器输入url回车后 可以分为八步 解析url并且判断其合法性DNS解析url到指定的ip建立TCP连接&#xff08;3次握手&#xff09;发送HTTP请求&#xff08;请求行/头/体&#xff09;服务器处理收到的请求&#xff0c;将数据返回浏览器浏览器收到HTTP响应浏览器解析并渲染页…

Linux基本操作符(2)

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 关于Linux的操作符&#xff0c;在上篇博客中我们已经讲述了一些&#xff0c;都是Linux最基本的操作符。今天我们继续了解一些关于对文件及目录增删查改的操作符&#xff0c;话不多说我们直接上内容。 目录 rm指令的回…

RAID知识点总结

目录 RAID类型 RAID的数据组织及存取方式 RAID热备与重构 RAID逻辑卷 常见的RAID RAID0 RAID 1 RAID3 RAID 5 RAID 6 RAID组合 RAID 10 RAID 50 总结 RAID技术对比 RAID的应用场景 RAID2.0 使用RAID2.0的原因 RAID2.0的发展 RAID2.0技术&#xff1a;两层虚拟…

【C语言数据结构——————栈和队列4000字详解】

欢迎阅读新一期的c语言数据结构模块————栈和队列 ✒️个人主页&#xff1a;-_Joker_- &#x1f3f7;️专栏&#xff1a;C语言 &#x1f4dc;代码仓库&#xff1a;c_code &#x1f339;&#x1f339;欢迎大佬们的阅读和三连关注&#xff0c;顺着评论回访&#x1f339;&#…

【Redis】简单动态字符串SDS

C语言字符串 char *str "redis"; // 可以不显式的添加\0&#xff0c;由编译器添加 char *str "redis\0"; // 也可以添加\0代表字符串结束C语言中使用char*字符数组表示字符串&#xff0c;‘\0’来标记一个字符串的结束&#xff0c;不过在使用的过程中我…