Spring AOP—深入动态代理 万字详解(通俗易懂)

目录

一、前言

二、动态代理快速入门

        1.为什么需要动态代理? : 

        2.动态代理使用案例:

        3.动态代理的灵活性 : 

三、深入动态代理

        1.需求 : 

        2.实现 : 

            2.1 接口和实现类

            2.2 提供代理对象的类

            2.3 测试类

        3.引出AOP :  

四、总结


一、前言

  • 第四节内容,up打算和大家分享Spring 动态代理 相关的内容动态代理本质上是Spring AOP的一个前置的引入内容,一个AOP开篇之作,但它却相当重要,且本身难度较大
  • 注意事项——①代码中的注释也很重要;不要眼高手低,自己跟着过一遍才真正有收获;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
  • 良工不示人以朴,up所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!

二、动态代理快速入门

        1.为什么需要动态代理? : 

        在日常开发中,往往存在这样一种需求——同时存在多个对象,这些对象对应的类都实现了同一接口,并且这些对象会去调用这个接口中的某一个方法,即多态,但是我们要求这几个对象在调用方法前,和调用方法后都要做一些业务处理,eg : 权限校验、事务管理、日志管理、安全校验等。

        如果我们将这些相同的业务处理,都下沉到每一个具体的类,就会造成代码冗余,并且没有办法进行对象的统一管理和调用;而动态代理的出现,尤对其症地解决了这个问题。

        2.动态代理使用案例:

                up先在dynamic_proxy包下创建Animal接口,以及Cat, Dog类,Cat类和Dog类都实现了Animal接口。如下图所示 : 

                Animal接口代码如下 : 

package com.cyan.spring.dynamic_proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public interface Animal {public abstract void eat();
}

                Cat类代码如下 :  

package com.cyan.spring.dynamic_proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public class Cat implements Animal{@Overridepublic void eat() {System.out.println("小猫爱吃鱼捏~");}
}

                Dog类代码如下 : 

package com.cyan.spring.dynamic_proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public class Dog implements Animal{@Overridepublic void eat() {System.out.println("小狗爱吃骨头捏~");}
}

                然后,我们创建一个AnimalProxyProvider类,见名知意,这个类可以提供一个Animal接口的代理对象,所以,该类中肯定会定义一个方法,用来返回Animal接口的代理实例,当然,这个方法稍微有点复杂,大家可以借助up的代码注释逐渐理解。
                AnimalProxyProvider类代码如下 : (尤其注意匿名内部类中实现的invoke方法,其中的两条输出语句表示实现类对象相同的业务逻辑代码

package com.cyan.spring.dynamic_proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public class AnimalProxyProvider {//利用接口类型对传入的对象做接收 (多态)private Animal targetAnimal;//通过带参构造传入一个Animal接口实现类的对象public AnimalProxyProvider(Animal targetAnimal) {this.targetAnimal = targetAnimal;}//编写一个方法,用于返回代理对象 (用到反射机制)public Animal getAnimalProxy() {/**java.lang.reflect.Proxy类中的 newProxyInstance方法可以返回一个代理对象。public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {...}该方法需要传入三个实参 ———(1) ClassLoader loader : 类加载器(2) Class<?>[] interfaces : 接口信息(3) InvocationHandler h : 调用处理器,其本身也是一个接口,内部声明了抽象方法invoke。*///(1)得到类加载器ClassLoader classLoader = targetAnimal.getClass().getClassLoader();//(2)得到被执行对象的接口信息(因为newProxyInstance方法底层是通过接口来调用的,即接口多态)Class<?>[] interfaces = targetAnimal.getClass().getInterfaces();//(3)得到处理器对象(通过匿名内部类实现,最终返回的是一个匿名内部类对象)//!!![注意,处理器对象本身也是newProxyInstance方法的一个形参]InvocationHandler invocationHandler = new InvocationHandler() {/*** @param proxy the proxy instance that the method was invoked on** @param method the {@code Method} instance corresponding to* the interface method invoked on the proxy instance.  The declaring* class of the {@code Method} object will be the interface that* the method was declared in, which may be a superinterface of the* proxy interface that the proxy class inherits the method through.** @param args an array of objects containing the values of the* arguments passed in the method invocation on the proxy instance,* or {@code null} if interface method takes no arguments.* Arguments of primitive types are wrapped in instances of the* appropriate primitive wrapper class, such as* {@code java.lang.Integer} or {@code java.lang.Boolean}.** @return : the results of method instance invoked* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*将相同的业务逻辑代码,放在处理器对象的invoke对象中,避免了代码下沉到每个实现类所造成的代码冗余。*/System.out.println("要吃饭的嘛~");//通过反射调用实现类中的方法Object results = method.invoke(targetAnimal, args);System.out.println("吃完了捏~");return results;}};Animal animalProxy = (Animal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);return animalProxy;}
}

                接着,up定义一个测试类,在测试类中定义一个单元测试方法,用以测试我们的动态代理是否生效。
                TestAnimal类代码如下 : 

package com.cyan.spring.dynamic_proxy;import org.junit.jupiter.api.Test;public class TestAnimal {@Testpublic void testGeyAnimalProxy() {//构造接口多态Animal animal = new Cat();Animal animal2 = new Dog();//传入需要被代理的对象AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);AnimalProxyProvider animalProxyProvider2 = new AnimalProxyProvider(animal2);//得到代理对象/*注意!此处代理对象animalProxy的编译类型仍然是Animal类型,但运行类型却不再是Cat or Dog类型,而是代理类型 ——— class com.sun.proxy.$Proxy9。*/Animal animalProxy = animalProxyProvider.getAnimalProxy();Animal animalProxy2 = animalProxyProvider2.getAnimalProxy();System.out.println("animalProxy's RuntimeType = " + animalProxy.getClass());System.out.println("animalProxy2's RuntimeType = " + animalProxy2.getClass());//通过代理对象调用实现类方法animalProxy.eat();System.out.println("==============================");animalProxy2.eat();}
}

                运行结果 : 

                现在我们进行Debug断点调试,断点如下图所示 : 

                跳入eat()方法时会发现,IDEA直接跳到了AnimalProxyProvider类的getAnimalProxy方法中——匿名内部类实现的invoke方法里面,如下图所示 : 

                再往下执行便是通过反射调用对应的method,一直往下追,最终会跳到实现类的eat()方法中,如下图所示 : 

        3.动态代理的灵活性 : 

                动态代理的灵活性体现在哪里?
                首先,被代理的对象是可变的。并且,代理对象所调用的方法也是可变的

                比方说,up在Animal接口中新定义了一个sleep方法,如下图所示 : 

                然后,up在Cat类中实现了sleep方法,如下图所示 : 

                接着,修改匿名内部类实现的invoke方法中的“业务逻辑”代码,如下图所示 : 

                最后,up在测试类中新定义一个单元测试方法,测试动态代理是否生效。
                testSleep()方法代码如下 : 

    @Testpublic void testSleep() {//1.构造接口多态Animal animal = new Cat();//2.传入需要被代理的对象AnimalProxyProvider animalProxyProvider = new AnimalProxyProvider(animal);//3.获取代理对象Animal animalProxy = animalProxyProvider.getAnimalProxy();//4.通过代理对象调用实现类方法String sleepMinutes = animalProxy.sleep(Long.valueOf(1314));}

                运行结果 : 


三、深入动态代理

        1.需求 : 

        定义Calculator接口,表示一个计算器,该接口中定义有可以完成简单加减乘除运算的方法,要求在每次执行运算方法前后,都打印出运算日志(运算法则和运算参数,以及运算结果)

        2.实现 : 

            2.1 接口和实现类

                首先分析需求,既然要求在每次执行运算方法前后都打印出运算日志,显然我们会想到——仍是在匿名内部类实现的invoke方法中动手脚。
                别的不说,先来定义Calculator接口和一个它的实现类
                Calculator接口如下 : (声明了“加减乘除”四个抽象方法)

package com.cyan.spring.dynamic_proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public interface Calculator {public abstract double add(double n1, double n2);public abstract double subtract(double n1, double n2);public abstract double multiply(double n1, double n2);public abstract double divide(double n1, double n2);
}

                定义一个实现类Calculator_Demo1,代码如下 : 

package com.cyan.spring.dynamic_proxy;/*** @author : Cyan_RA9* @version : 21.0*/
public class Calculator_Demo1 implements Calculator {@Overridepublic double add(double n1, double n2) {return n1 + n2;}@Overridepublic double subtract(double n1, double n2) {return n1 - n2;}@Overridepublic double multiply(double n1, double n2) {return n1 * n2;}@Overridepublic double divide(double n1, double n2) {//分母不允许为0if (n2 != 0) {return n1 / n2;}return -1;}
}

            2.2 提供代理对象的类

                定义一个CalculatorProxyProvider类,与上文 “动态代理使用案例” 中定义的“提供代理对象的类”类似,都需要定义一个方法用于返回代理对象。
                CalculatorProxyProvider类代码如下 : 

package com.cyan.spring.dynamic_proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;public class CalculatorProxyProvider {private Calculator calculator;public CalculatorProxyProvider(Calculator calculator) {this.calculator = calculator;}/*底层仍然使用java.lang.reflect包下的Proxy类的newProxyInstance方法来获取代理对象。*/public Calculator getCalculatorProxy() {//1.获取newProxyInstance方法的第一个参数————类加载器ClassLoader classLoader = calculator.getClass().getClassLoader();//2.获取newProxyInstance方法的第二个参数————接口信息Class<?>[] interfaces = calculator.getClass().getInterfaces();//3.获取newProxyInstance方法的第三个参数————处理器对象//仍然借助匿名内部类来实现,并通过构造接口多态的形式做接收。InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获取到当前传入的参数double n1 = (double) args[0];double n2 = (double) args[1];//获取当前方法名String name = method.getName();//定义运算结果double result = 0.0;try {//Δ在运算方法执行前打印出运算日志System.out.println("运算日志————运算法则 = " + name + ",传入两个参数分别是 " + n1 + " 和 " + n2);//执行运算result = (double) method.invoke(calculator, args);System.out.println("result = " + result);//Δ在运算方法执行后打印出运算日志System.out.println("运算日志————运算法则 = " + name + ",运算结果 = " + result);//返回运算结果return result;} catch (Exception e) {System.out.println("异常日志————" + LocalDateTime.now() + ",方法" + method.getName() + "执行异常");throw new RuntimeException(e);} finally {System.out.println("执行日志————" + method.getName() + "方法执行结束。");}}};//4.调用newProxyInstance方法,得到代理对象Calculator instance = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);//5.返回获得的代理对象return instance;}
}

            2.3 测试类

                最后,仍然是在测试类中定义一个单元测试方法,up新定义了一个TestCalculator类,代码如下 : 

package com.cyan.spring.dynamic_proxy;import org.junit.jupiter.api.Test;public class TestCalculator {@Testpublic void testArithmetic() {//1.构造接口多态Calculator calculator = new Calculator_Demo1();//2.传入需要被代理的对象CalculatorProxyProvider calculatorProxyProvider = new CalculatorProxyProvider(calculator);//3.获取代理对象Calculator calculatorProxy = calculatorProxyProvider.getCalculatorProxy();//4.通过代理对象调用实现类方法double addResult = calculatorProxy.add(200.333, 33);System.out.println("-----------------------------");double subtractResult = calculatorProxy.subtract(141, 5);System.out.println("-----------------------------");double multiplyResult = calculatorProxy.multiply(11.11, 2);System.out.println("-----------------------------");double divideResult = calculatorProxy.divide(3917.0, 500.00);}
}

                运行结果 : 

        3.引出AOP :  

                注意——
                (1) CalculatorProxyProvider类的这段代码,如下图所示 : 

                在AOP中,称为“横切关注点”,也叫“前置通知

                (2) 而下面的这段代码,如下图所示 : 

                从AOP的角度来看,也称为一个“横切关注点”,但也叫“返回通知

                (3) 此外,异常处理——catch语句块中的这段代码,如下图所示 : 

                从AOP看,也称为一个“横切关注点”,但又称为“异常通知”。

                (4) 最后,finally代码块中的内容,如下图所示 : 

                从AOP看,也称为一个“横切关注点”,但又称为“后置通知

                分析一下我们方才写得代码,如下图所示 : 

                可以看到,无论是“前置通知”,“后置通知”,还是“异常通知”,“最终通知”。我们都只是草草地用了一条输出语句敷衍过去,这使得我们的代码不够牛逼,功能不够强大,且代码死板,不够灵活。
                而作为一名OOP程序员,我们会容易联想到——假如此处的输出语句都替换成方法,用一个方法直接切入,那不就既满足灵活性,又可以实现强大的功能吗?


四、总结

  • 🆗,以上就是Spring系列博文第四小节的全部内容了。
  • 总结一下,我们应该明白动态代理究竟“动态”在哪里?—— 不止是被代理对象可变,且代理对象执行的方法也是可变的。我们还需要知道newProxyInstance方法的三个形参分别有什么作用,以及如何获取这三个形参(尤其是第三个形参——处理器对象的获取,用到了匿名内部类)。总之,这篇博文其实是为了给“Spring—AOP”的分享做一个铺垫,我们以一个问题开始,又以一个问题结尾,也符合这篇博客的定位。
  • 下一节内容——Spring AOP—切入表达式和基于XML配置AOP,我们不见不散。感谢阅读!

        System.out.println("END---------------------------------------"); 

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

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

相关文章

【算法】数论---欧拉函数

什么是欧拉函数&#xff1f; 对于正整数n&#xff0c;欧拉函数是小于或等于n的正整数中与n互质的数的数目&#xff0c;记作φ(n) φ(1)1 当m,n互质时&#xff0c;φ(mn)φ(m)∗φ(n) 一、求一个正整数的欧拉函数---&#xff08;先对它分解质因数&#xff0c;然后套公式&#xf…

elementui+vue2 input输入框限制只能输入数字

方法1 自定义表单校验 <el-form :model"Formdata" ref"formRef" :rules"nodeFormRules" label-width"100px"><el-form-itemlabel"年龄"prop"age"><el-input v-model.number"Formdata.age&q…

每日一练(编程题-C/C++)

目录 CSDN每日一练1. 2023/2/27- 一维数组的最大子数组和(类型&#xff1a;数组 难度&#xff1a;中等)2. 2023/4/7 - 小艺照镜子(类型&#xff1a;字符串 难度&#xff1a;困难)3. 2023/4/14 - 最近的回文数(难度&#xff1a;中等)4. 2023/2/1-蛇形矩阵(难度&#xff1a;困难)…

flask文件夹列表改进版--Bug追踪

把当前文件夹下的所有文件夹和文件列出来&#xff0c;允许点击返回上层目录&#xff0c;允许点击文件夹进入下级目录并显示此文件夹内容 允许点击文件进行下载 from flask import Flask, render_template, send_file, request, redirect, url_for import osapp Flask(__name_…

QT编译并部署QtMqtt相关环境+跑测demo【超详细教程】

文章目录 概要整体架构流程▷下载指定版本的QMqtt源码&#xff1a;▷编译后同步MQTT相关文件&#xff1a; 技术名词解释技术实现步骤详解一、编译源码1、编译报错2、解决思路3、编译通过 二、继续完善mqtt应用环境1、打开编译生成的shadow build文件夹2、同步lib3、同步bin4、同…

FA对接FC流程

2、FA进行对接 &#xff08;1&#xff09;首先安装好AD域控服务器DHCPDNS&#xff08;注意&#xff0c;不要忘记了做DNS正反向解析&#xff0c;就是把已经安装了ITA的主机做解析&#xff09;&#xff0c;在里面创建域用户 &#xff08;2&#xff09;安装ITA和VAG/VLB&#xf…

4.32 构建onnx结构模型-Erf

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Erf 结点进行分析 方式 方法一&…

R306指纹识别模块指令系统

一&#xff1a;指令集 1. GR_GetImage 指令代码&#xff1a;01H 功能&#xff1a;从传感器上读入图像存于图像缓冲区 2. GR_GenChar 指令代码&#xff1a;02H 功能&#xff1a;根据原始图像生成指纹特征存于 CharBuffer1 或 CharBuffer2 3. GR_Match 指令代码&#xff…

【操作系统xv6】学习记录1

前置说明&#xff1a; git-v9版本&#xff1a;git clone https://github.com/mit-pdos/xv6-public/tree/xv6-rev9 bili:https://www.bilibili.com/video/BV15r4y1z75F 深圳大学罗秋明老师的课程 我自己用的wsl2的ubuntu18 无桌面版本 make qemu-nox bug 起初在双系统的ubuntu…

matlab列优先与高维矩阵重构

由于matlab在列化a(:)以及reshape(a)等操作中是列优先的&#xff0c;所以要重构出新的高维度矩阵&#xff0c;通常要把reshape和permute结合起来使用。 先到 http://caffe.berkeleyvision.org/ 下载 训练好的model bvlc_reference_caffenet.caffemodel; 更多caffe使用也请参看…

分布式【雪花算法】

雪花算法 背景&#xff1a;在分布式系统中&#xff0c;需要使用全局唯一ID&#xff0c;期待ID能够按照时间有序生成。 **原理&#xff1a;**雪花算法是 64 位 的二进制&#xff0c;一共包含了四部分&#xff1a; 1位是符号位&#xff0c;也就是最高位&#xff0c;始终是0&am…

Python数值型字符串校验(try异常拦截解析)

从键盘输入一行字符串&#xff0c;编写Python代码判定字符串是python“合法”数值。 (笔记模板由python脚本于2023年12月25日 18:00:52创建&#xff0c;本篇笔记适合熟悉Python符串基本数据类型的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.py…

docker +gitee+ jenkins +maven项目 (一)

jenkins环境和插件配置 文章目录 jenkins环境和插件配置前言一、环境版本二、jenkins插件三、环境安装总结 前言 现在基本都是走自动化运维&#xff0c;想到用docker 来部署jenkins &#xff0c;然后jenkins来部署java代码&#xff0c;做到了开箱即用&#xff0c;自动发布代码…

【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

DevOps持续交付之容器化CICD流水线

DevOps持续交付 随着DevOps⼤规模化的落地和应⽤&#xff0c;持续集成以及持续交付已经是⼀种常态的。CI指的是持续集成&#xff0c;使⽤的开源⼯具是Jenkins&#xff0c;CD指的是持续交付和持续部署&#xff0c;⼀个完整的软件开发⽣命周期为: 主要流程可以具体为: 构建阶段…

【K8S 部署】基于kubeadm搭建Kurbernetes集群

目录 一、基本架构 二、环境准备: 三、安装部署 1、所有节点安装docker 2、、所有节点安装kubeadm&#xff0c;kubelet和kubectl 3、配置网络--flannel 4、测试 pod 资源创建 四、安装部署与k8s集群对接的Harbor仓库 五、Dashboard安装部署&#xff1a; 一、基本架构…

mac 生成 本地.ssh

输入下面命令行 ssh-keygen 默认回车得到下面的 Generating public/private rsa key pair. Enter file in which to save the key (/Users/{用户名}/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has be…

论文阅读——SG-Former

SG-Former: Self-guided Transformer with Evolving Token Reallocation 1. Introduction 方法的核心是利用显著性图&#xff0c;根据每个区域的显著性重新分配tokens。显著性图是通过混合规模的自我关注来估计的&#xff0c;并在训练过程中自我进化。直观地说&#xff0c;我们…

文件监控-IT安全管理软件

文件监控和IT安全管理软件是用于保护企业数据和网络安全的工具。这些工具可以帮助企业监控文件的变化&#xff0c;防止未经授权的访问和修改&#xff0c;并确保数据的安全性和完整性。 一、具有哪些功能 文件监控软件可以实时监控文件系统的活动&#xff0c;包括文件的创建、修…

C++继承与派生——(8)多继承

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 苦难和幸福一样&#xff0c;都是生命盛…