Spring中AOP详解
目录
一、AOP的概念
二、AOP的底层实现原理
2.1 JDK的动态代理
2.1.1 invocationhandler接口
2.1.2 代理对象和原始类实现相同的接口 interfaces
2.1.3 类加载器ClassLoador
2.1.4 编码实现
2.2 Cglib动态代理
2.2.1 Cglib动态代理编码实现
三、AOP如何通过原始对象的id获取到代理对象
3.1 BeanPostProcessor
3.2 编码实现
一、AOP的概念
AOP(Aspect Oriented Programing)即面向切面编程,以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。这里的 切面 = 切入点 + 额外功能,所以我们常说的AOP也就等同于Spring中的动态代理开发!那么什么是切面呢?当在不同的ServiceImpl中,需要添加同一个额外功能的时候,这几个类的方法中所添加的相同额外功能就会由点构成面,所以就将这个称为是切面

二、AOP的底层实现原理
2.1 JDK的动态代理
由于这里是探索AOP底层的实现原理,所以我们这里先摒弃Spring框架。首先我们需要了解代理创建的三个要素(1.原始对象 2.额外功能 3.代理对象和原始对象实现相同的接口),有了这三个要素之后就能创建出一个代理对象,接下来画图分析
首先将这个原始对象创建出来,在添加额外功能和实现相同的接口的时,使用JDK的Proxy类中的newProxyInstance(动态字节码技术)方法来完成。要了解一个类中方法的具体使用,就需要了解这个类中参数的具体含义

2.1.1 invocationhandler接口
这里的invocationhandler接口就是完成额外功能的,实现这个接口时要实现这个invoke方法,提到这个invoke方法是不是就联想到了Spring中的拦截器MethodInterceptor中的invoke方法?其实MethodInterceptor中的invoke方法就是对这一系列的操作进行了封装
在invocationHandler接口中的invoke方法有三个参数,其中proxy忽略掉
method:额外功能所增加给的原始方法
args:原始方法的参数
method调用其invoke方法使得原始方法运行起来,那么我们想要添加额外功的就只需要添加在method.invoke的前后即可。这样额外功能的添加就完成了

2.1.2 代理对象和原始类实现相同的接口 interfaces
这里的参数interfaces是获取到原始对象实现的那个接口。通过获取类文件在获取接口来实现

2.1.3 类加载器ClassLoador
在一般创建对象的过程都是通过类加载器将对应的字节码文件加载到JVM,同时类加载器创建类的class对象,进而创建出这个类的对象。其中CL表示类加载器,同时获取这个类加载器也不需要我们担心,每一个类的.class文件都会自动分配一个

但是在创建动态代理类的时候是没有源文件的,它是通过动态字节码技术(Proxy.newProxyInstance)去创建字节码的。由于动态代理技术是直接将字节码文件写入JVM中的,并没有这个类加载器,但是我们又需要使用类加载器去帮我们创建代理类对象,那这个时候怎么办呢?借一个嘛!所以这就是参数中需要一个类加载器的原因

2.1.4 编码实现
创建接口
public interface UserService {void register();boolean login();
}
原始方法实现这个类
public class UserServiceImpl implements UserService{@Overridepublic void register() {System.out.println("register核心功能正在执行");}@Overridepublic boolean login() {System.out.println("login核心功能正在执行");return false;}
}
添加额外功能
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class TestJDKProxy {public static void main(String[] args) {// 创建原始对象UserService userService = new UserServiceImpl();// 以下是JDK动态代理创建// 实现InvocationHandler接口,为了方便演示采取内部类的方式InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 原始方法运行Object ret = method.invoke(userService,args);// 在原始方法后面添加额外功能System.out.println("aop底层实现----额外功能添加在原始功能后面----log");return ret;}};// TestJDKProxy.class.getClassLoader()借用一个类加载器,借谁的无所谓// userService.getClass().getInterfaces() 拿到原始类的接口// 使用相同的接口接收代理类UserService userServiceProxy =(UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);// 调用核心方法 观察额外功能是否添加完成userServiceProxy.login();userServiceProxy.register();}
}

至此,JDK的动态代理原理就已经全部分析完了
2.2 Cglib动态代理
首先在开始Cglib动态代理之前,我们在回顾以下JDK动态代理的过程。JDK动态代理类通过与原始类实现同一个接口从而完成额外功能的添加。但是在现实开发的过程中有没有一种可能这个原始类没有实现任何的接口,那这个时候该怎么办呢?这个时候就需要使用Cglib动态代理来完成了

那Cglib是怎么完成这个代理类的实现的呢?Cglib是采取了继承的方式来完成代理类的实现的

由于这里的Cglib动态代理的实现与JDK动态代理的实现是高度一致的,这里就只介绍二者的区别了,而不再介绍相同点了
2.2.1 Cglib动态代理编码实现
在Cglib动态代理的实现中是通过Enhancer类中的一系列方法来完成的,通过setClassLoder方法去设置类加载器,通过setSuperClass方法设置父类对象,通过setCallback方法设置额外功能,当然这个额外功能也要去实现接口,这里的接口是MethodInterceptor(这个并不是Spring提供的那个接口,而是Cglib包中的接口),最后通过create方法创建动态代理对象
public class UserServiceImpl{public void register() {System.out.println("register核心功能正在执行");}public boolean login() {System.out.println("login核心功能正在执行");return false;}
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TestCglib {public static void main(String[] args) {// 创建原始对象UserServiceImpl userService = new UserServiceImpl();// 以下是Cglib创建动态代理对象Enhancer enhancer = new Enhancer();// TestCglib.class.getClassLoader() 借用的类加载器enhancer.setClassLoader(TestCglib.class.getClassLoader());// userService.getClass() 获取到的父类对象enhancer.setSuperclass(userService.getClass());MethodInterceptor interceptor = new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 添加额外功能System.out.println("Cglib底层实现------额外功能添加在方法执行前---log");// 原始方法执行Object ret = method.invoke(userService, args);return ret;}};// 设置额外功能enhancer.setCallback(interceptor);// 创建动态代理对象UserServiceImpl userServiceCglib = (UserServiceImpl) enhancer.create();userServiceCglib.register();userServiceCglib.login();}
}
三、AOP如何通过原始对象的id获取到代理对象
3.1 BeanPostProcessor
在Spring中提供了一个接口BeanPostProcessor,这个接口是用来加工Spring通过配置文件创建的对象的。通过实现接口中的postProcessorAfterInitialization方法,就可以实现通过原始对象的id值获取到代理对象了(也就是通过这个接口对原始类进行再加工)

3.2 编码实现
首先在Spring的配置文件中创建UserServiceImpl的对象,这里是实现的接口,所以动态代理应该使用JDK动态代理的方式
public class UserServiceImpl implements UserService{@Overridepublic void register() {System.out.println("register核心功能正在执行");}@Overridepublic boolean login() {System.out.println("login核心功能正在执行");return false;}
}
<bean id="userService" class="com.gl.demo.proxy.UserServiceImpl"/>
创建好对象以后,创建一个类实现BeanPostProcessor接口为原始类进行加工,进而将代理类返回给用户
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class BeanPostProcessorTest implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 在这个方法中对需要添加额外功能的类进行加工// 这里采取JDK动态代理的方式进行加工// BeanPostProcessorTest.class.getClassLoader() 借用的类加载器// bean.getClass().getInterfaces()获取原始类的接口// 实现InvocationHandler接口添加额外功能InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object ret = method.invoke(bean, args);System.out.println("spring底层实现动态代理----额外功能添加在原始方法后---log");return ret;}};// 将代理类返回给用户而不是原始类return Proxy.newProxyInstance(BeanPostProcessorTest.class.getClassLoader(),bean.getClass().getInterfaces(),handler);}
}
最后将加工的好的代理对象配置在Spring的配置文件中
<bean id="proxyBeanProcessor" class="com.gl.demo.proxy.BeanPostProcessorTest"/>
这时候,用户通过原始类的id值拿到的是代理类而不是原始类了,进而完成了动态代理的过程
public void test4() {ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config2.xml");UserService userService = (UserService) ctx.getBean("userService");userService.register();userService.login();
}

至此,AOP底层原理就已经全部分析完毕了!以上的工作Spring其实都给我们封装好了,在日后的开发过程中直接使用就可以了,不用这么麻烦!
相关文章:
Spring中AOP详解
目录 一、AOP的概念 二、AOP的底层实现原理 2.1 JDK的动态代理 2.1.1 invocationhandler接口 2.1.2 代理对象和原始类实现相同的接口 interfaces 2.1.3 类加载器ClassLoador 2.1.4 编码实现 2.2 Cglib动态代理 2.2.1 Cglib动态代理编码实现 三、AOP如何通过原始对象的id获取到代…...
Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析
最近DOTS发布了正式的版本, 我们来分享一下DOTS里面Baking核心机制,方便大家上手学习掌握Unity DOTS开发。今天给大家分享的Baking机制中的Filter Baking Output与Prefab In Baking。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础…...
Matlab读写操作
随机生成一个3*3矩阵,对矩阵进行按列升序排列 >> Arand(3,3); >> [B, ~] sort(A, 2); >> B B 0.4898 0.6797 0.70940.4456 0.6551 0.75470.1626 0.2760 0.6463在不同数值类型下显示π的值 1、默认数值类型 >> p_defa…...
Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】
给定部分完成的MusicPlayer项目,实现其中未完成的service部分: 1、创建MusicService类,通过service组件实现后台播放音乐的功能; 2、在MainActivity中通过ServiceConnection连接MusicService,实现对音乐播放的控制&…...
使用Windows平台的Hyper-V虚拟机安装CentOS7的详细过程
Hyper-V虚拟机安装CentOS7 前言常见Linux系统CentOSUbuntuDebianKaliFedoraArch LinuxMintManjaroopenSUSE Hyper-V开启Hyper-V打开Hyper-V Hyper-V的使用新建虚拟机开始安装分区配置开始安装 修改yum源为阿里源 前言 作为一名开发者,就服务器而言,接触最…...
某马机房预约系统 C++项目(二) 完结
8.4、查看机房 8.4.1、添加机房信息 根据案例,我们还是先在computerRoom.txt中直接添加点数据 //几机房 机器数量 1 20 2 50 3 1008.4.2、机房类创建 同样我们在头文件下新建一个computerRoom.h文件 添加如下代码: #pragma once #include<i…...
npm 安装到指定文件夹
创建一个文件夹,用vscode或者cmd打开, 执行 npm install --prefix ./ 路径 包名, npm install --prefix ./ 包名 , 就会将包安装在当前文件夹, 例如: npm install --prefix ./ -g oppo-minigame…...
自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例
自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例 在matlab中有自带的离散傅里叶变换程序,即fft程序,但该程序是封装的,无法看到源码。为了比较清楚的了解matlab自带的实现过程,本文通过自建程序实现matlab程序&…...
Vue图片路径问题(动态引入)
vue项目中我们经常会遇到动态路径的图片无法显示的问题,以下是静态路径和动态路径的常见使用方法。 1.静态路径 在日常的开发中,图片的静态路径通过相对路径和绝对路径的方式引入。 相对路径:以.开头的,例如./、../之类的。就是…...
项目部署Linux步骤
1、最小化安装centos7-环境准备 安装epel-release 安装epel-release,因为有些rpm包在官方库中找不到。前提是保证可以联网 yum install -y epel-release 修改IP net-tools net-tool:工具包集合,包含ifconfig等命令 yum install -y net-…...
UG\NX二次开发 在资源栏(左侧面板)中添加按钮
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 apolloryd 订阅本专栏,非常感谢。 简介 UG\NX二次开发 在资源栏(左侧面板)中添加按钮,下面提供了帮助说明,在 UGOPEN 文件夹下有示例。 C++语言在UG二次…...
Proteus仿真--量程自动切换数字电压表(仿真+程序)
本文主要介绍基于51单片机的量程自动切换数字电压表Proteus仿真设计(完整仿真源文件及代码见文末链接) 简介 硬件电路主要分为单片机主控模块、AD转换模块、量程选择模块以及数码管显示模块 (1)单片机主控模块:单片…...
如何使用ArcGIS Pro制作一张地形图
01数据来源 本教程所使用的数据是从水经微图中下载的DEM数据,除了DEM数据,常见的GIS数据都可以从水经微图中下载,你可以通过关注“水经注GIS”,然后在后台回复“微图”即可获取软件下载地址,当然也可以直接在水经注…...
人工智能三要数之算法Transformer
1. 人工智能三要数之算法Transformer 人工智能的三个要素是算法、数据和计算资源。Transformer 模型作为一种机器学习算法,可以应用于人工智能系统中的数据处理和建模任务。 算法: Transformer 是一种基于自注意力机制的神经网络模型,用于处理序列数据的…...
Java ThreadPoolExecutor 线程池
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ArrayBlockingQueue;public class ThreadPoolExample {public static void main(String[] args) {// 创建线程池对象ThreadPoolExecutor threadPool new…...
网络协议--IP选路
9.1 引言 选路是IP最重要的功能之一。图9-1是IP层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生,也可以由其他主机产生。在后一种情况下,主机必须配置成一个路由器,否则通过网络接口接收到的数据报,如果目的地址不…...
使用udevil自动挂载U盘或者USB移动硬盘
最近在折腾用树莓派(实际上是平替香橙派orangepi zero3)搭建共享文件服务器,有一个问题很重要,如何在系统启动时自动挂载USB移动硬盘。 1 使用/etc/fstab 最开始尝试了用/etc/fstab文件下增加:"/dev/sda1 /home/orangepi/s…...
学习笔记二十二:K8s控制器Replicaset
K8s控制器Replicaset Replicaset控制器:概念、原理解读Replicaset概述Replicaset工作原理:如何管理PodReplicaset控制器三个组成部分 Replicaset资源清单文件编写技巧Replicaset使用案例:部署Guestbook留言板编写一个ReplicaSet资源清单资源清…...
2023-10-25 精神分析-领悟新技术的错误做法-持续数年的错误做法-记录与分析
摘要: 过去数年对于领悟技术, 采取的做法不能说是对达到目的毫无裨益,但是对突破技术和将技术融为自身这个目的来说, 没有达到。 而且随着时间的流逝, 过去已经熟悉的技术, 竟然会被忘掉!就像是没有涉猎过一样! 根本原因出在对技术的领悟的…...
Arrays 中的 asList()方法
public static <T> List<T> asList( T . . . a ){ return new ArrayList<>(a); } 返回由指定数组支持的固定大小的 list集合。对数组所做的更改将在返回的 l…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
