模拟实现 Spring AOP

news2024/11/25 2:55:42

文章目录

  • 前言
  • Spring AOP
    • Spring AOP 概述
    • Spring IoC 技术难点
    • Spring IoC 框架思考
      • 需求分析
  • Spring IoC 技术难点实现
  • 模拟实现 AOP 具体代码

前言

Spring 是一种 Java 开发框架,其主要功能有两个:IoC(DI)和AOP。《模拟实现Spring AOP》是本人的一个编程训练项目,为了提升本人的编程能力、JAVA 编程思想,基于框架的角度出发,对 Spring AOP有一个更深层次的认识,动态代理模式的底层实现逻辑有更深的理解。

博主本人初入 Java 不久,能力有限,只将 Spring AOP完成到:实现了基于代理机制的拦截器链以及基于正则的配置;

Spring AOP

AOP(面向切面编程),通过预编译方式和运行期动态代理来实现程序功能的统一与扩展的技术。

Spring AOP 概述

AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,它的主要目的是将通用功能从业务逻辑中抽离出来,以达到代码复用和模块化的目的。AOP的基本概念包括:

  • 切面(Aspect):切面是一个模块化的关注点,通常包含一个或多个通知(Advice)和切入点(Pointcut)的组合。切面的作用是将横切关注点模块化,以便于重用和维护。
  • 通知(Advice):通知是切面中执行的具体操作。通知有五种类型:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  • 切入点(Pointcut):切入点是一个表达式,用于定义在哪些方法上应用通知。切入点可以使用通配符匹配方法名、参数类型和返回值类型。
  • 连接点(JoinPoint):连接点是程序执行过程中的某个特定的点,例如方法执行、异常抛出等。切面可以在连接点附近织入通知。
  • 织入(Weaving):织入是将切面代码插入到目标类中的过程。织入可以在编译期(Compile-time)、类加载期(Load-time)或运行期(Runtime)完成。

AOP可以对业务逻辑部分进行隔离,从而使业务逻辑耦合降低,提高代码复用和开发效率。是基于动态代理实现的,如果目标对象实现了接口,就用 JDK 动态代理,未实现接口就用 CGLIB 动态代理。通过 AOP 技术,在不修改源代码的情况下,为程序添加了新的功能,是对程序的非侵入式扩展。

Spring IoC 技术难点

  • 拦截器的实现(代理模式的应用)
  • 基于正则的配置

Spring IoC 框架思考

需求分析

  • 代理对象的获取

    • JDKProxy 代理模式实现

      public class JDKProxy {
      	private static IntercepterChain chain;
      
      	public JDKProxy() {
      	}
      	
      	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
      		JDKProxy.chain.addIntercepter(targetMethod, intercepter);
      	}
      
      	public <T> T getProxy(Object object) {
      		Class<?> klass = object.getClass();
      		ClassLoader loader = klass.getClassLoader();
      		Class<?>[] interfaces = klass.getInterfaces();
      		
      		InvocationHandler h = new InvocationHandler() {
      			@Override
      			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      				return JDKProxy.chain.doInvoker(object, method, args);
      			}
      		};
      		
      		@SuppressWarnings("unchecked")
      		T proxy = (T) Proxy.newProxyInstance(loader, interfaces, h);
      		
      		return proxy;
      	}
      }
      
    • CGLibProxy 代理模式实现

      public class CGLibProxy {
      
      	public CGLibProxy() {
      	}
      
      	@SuppressWarnings("unchecked")
      	public <T> T getProxy(Object object) {
      		Class<?> klass = object.getClass();
      		Enhancer enhancer = new Enhancer();
      		enhancer.setSuperclass(klass);
      		
      		enhancer.setCallback(new InvocationHandler() {
      			@Override
      			public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
      				return null;
      			}
      		});
      		
      		return (T) enhancer.create();
      	}
      }
      
  • 切点的匹配以及拦截器链的实现

  • 对代理对象执行方法的参数以及结果的获取与处理

Spring IoC 技术难点实现

  • 拦截器的实现(代理模式的应用)

    拦截器:是一种可以在方法执行前、后、异常发生时执行的逻辑,并且,这些逻辑可以“堆叠”,形成拦截器链,或者,拦截器堆栈。

    可以通过“增加拦截器”或者“删除拦截器”的方式,对拦截器进行编辑;拦截器应该是针对某个、某些方法的。

    无论如何,拦截器技术是基于代理机制的。那么,必须提供“取得代理对象”的方法。

    前置拦截器:前置拦截器可以决定是否继续执行目标方法,也就是说,前置拦截器应该允许返回逻辑值,以确定是否继续执行目标方法;而且,对于由多个拦截器组成的拦截器链,任何一个拦截器返回值若为 false,则,所有其后的前置拦截器,以及方法本身,都不再执行!这可以称为:方法的中断。

    通常情况下,前置拦截器按照拦截器添加顺序执行(即,队列模式————先进先服务);后置拦截器按照添加逆序执行(即,堆栈模式————先进后服务)。

    public class IntercepterChain {
    	private Intercepter intercepter;
    	private IntercepterChain next;
    	
    	public IntercepterChain() {
    		this.intercepter = null;
    		this.next = null;
    	}
    	//增加拦截器
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		if (this.intercepter == null) {
    			this.intercepter = new Intercepter(targetMethod, intercepter);
    			return;
    		}
    		
    		if (this.next == null) {
    			this.next = new IntercepterChain();
    		}
    		this.next.addIntercepter(targetMethod, intercepter);
    	}
    	//反射执行方法
    	public Object doInvoker(Object object, Method method, Object[] args) throws Throwable {
    		Object result = null;
    		
    		if (this.intercepter == null) {
    			return method.invoke(object, args);
    		}
    		
    		if (this.intercepter.before(method, args)) {
    			if (this.next != null) {
    				result = this.next.doInvoker(object, method, args);
    			}
    			
    			if (this.next == null) {
    				result = method.invoke(object, args);
    			}
    			
    			result = this.intercepter.after(method, result);		
    		}	
    		return result;
    	}
    }
    
  • 基于正则的配置

    由于想匹配到切点就必须提供方法的全类名以及参数的全类名,对于使用者来说很不方便,采用正则表达式来处理匹配切点。用户只需要提供一个普通的类名、或方法名及参数,对于其他的不确定的地方使用‘*’来代替,当用户调用到添加拦截器时,对于用户传入的简单切点名进行转化,MethodInvoker 类中提供了处理用户提交的普通的类名与参数名的 toRegex 方法,此方法把简单的字符串转换为正则表达式,从而找到符合的切面的切点。完成切点的匹配。

    /**
         * 用户提供切点名,*.ClassName.methodName(*)
         * 例:*.MyInterface.*(*String,*int) 表示任意长度的前一个字符,前面必须有字符
         *  故 .* 表示任意字符
         *  用户只需输入简单的参数以及方法名,不确定的地方用*代替
         *  .*IMyInterface..*\(.*String,.*int\)
         *
         */
        private String toRegex(String methodName) {
            StringBuffer stringBuffer = new StringBuffer();
            //将不确定的地方替换为.*表示匹配任意字符
            String buffer = methodName.replace("*", ".*");
     
            int left =buffer.indexOf("(");
            int right = buffer.indexOf(")");
            //得到类名与方法名
            stringBuffer.append(".*").append(buffer.substring(0, left)).append("\\(");
     
            //按照逗号分割出参数字符串数组
            String[] args = buffer.substring(left + 1, right).split(",");
            for(int i = 0; i < args.length; i++) {
                stringBuffer.append((i == 0 ? "" : ",")).append(".*").append(args[i]);
            }
            stringBuffer.append("\\)");
            return stringBuffer.toString();
        }
    

模拟实现 AOP 具体代码

为了在目标方法前后执行新的方法,且不对源代码进行修改,所以要用动态代理,让代理去执行目标方法,然后在执行方法前进行前置拦截,在执行方法后进行后置拦截。

  • JDK 动态代理

    public class JDKProxy {
    	private static IntercepterChain chain;
    
    	public JDKProxy() {
    	}
    	
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		JDKProxy.chain.addIntercepter(targetMethod, intercepter);
    	}
    
    	public <T> T getProxy(Object object) {
    		Class<?> klass = object.getClass();
    		ClassLoader loader = klass.getClassLoader();
    		Class<?>[] interfaces = klass.getInterfaces();
    		
    		InvocationHandler h = new InvocationHandler() {
    			@Override
    			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    				return JDKProxy.chain.doInvoker(object, method, args);
    			}
    		};
    		
    		@SuppressWarnings("unchecked")
    		T proxy = (T) Proxy.newProxyInstance(loader, interfaces, h);
    		
    		return proxy;
    	}
    }
    
  • Result 类

    public class Result {
    	private static Object result;
    	
    	Result() {
    	}
    	……getter、setter
    }
    
  • 拦截器链实现

    public class IntercepterChain {
    	private Intercepter intercepter;
    	private IntercepterChain next;
    	
    	public IntercepterChain() {
    		this.intercepter = null;
    		this.next = null;
    	}
    	//增加拦截器
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		if (this.intercepter == null) {
    			this.intercepter = new Intercepter(targetMethod, intercepter);
    			return;
    		}
    		
    		if (this.next == null) {
    			this.next = new IntercepterChain();
    		}
    		this.next.addIntercepter(targetMethod, intercepter);
    	}
    	//反射执行方法
    	public Object doInvoker(Object object, Method method, Object[] args) throws Throwable {
    		Object result = null;
    		
    		if (this.intercepter == null) {
    			return method.invoke(object, args);
    		}
    		
    		if (this.intercepter.before(method, args)) {
    			if (this.next != null) {
    				result = this.next.doInvoker(object, method, args);
    			}
    			
    			if (this.next == null) {
    				result = method.invoke(object, args);
    			}
    			
    			result = this.intercepter.after(method, result);		
    		}	
    		return result;
    	}
    }
    
    
  • 接口

    public interface IIntercepter {
    	boolean before();	//前置拦截
    	void after();		//后置拦截
    }
    
  • 拦截器

    //获取代理对象,并且在before()内部采用正则表达式来有选择的进行拦截处理。
    //当before()方法返回值为true时,则可以正常执行目标方法,否则不执行。
    public class Intercepter {
    	private String targetMethod;		//拦截目标
    	private IIntercepter intercepter;	
    	
    	Intercepter(String targetMethod, IIntercepter intercepter) {
    		this.targetMethod = targetMethod;
    		this.intercepter = intercepter;
    	}
    	//前置拦截:如果返回值为false则为不允许执行下面的方法
    	boolean before(Method method, Object[] args) {
    		// 正则,TODO
    		if (!method.toString().equals(this.targetMethod)) {
    			return true;
    		}
    		
    		Arguments.setArgs(args);
    		return this.intercepter.before();
    	}
    	后置拦截主要针对于方法执行的结果
    	Object after(Method method, Object result) {
    		// 正则,TODO
    		if (!method.toString().equals(this.targetMethod)) {
    			return result;
    		}
    		
    		Result.setResult(result);
    		this.intercepter.after();
    		
    		return Result.getResult();
    	}	
    }
    

这里只是作为本人训练项目,简单的实现了 Spring AOP 中的核心功能,还需要进行更深入的学习相关知识,有问题处望大佬指出。

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

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

相关文章

基于SSM的校园二手交易平台

一、源码获取&#xff1a; 链接点击直达&#xff1a;下载链接 二、系统架构&#xff1a; 使用技术&#xff1a; SpringSpringMVCMybatis 三、系统需求分析&#xff1a; 在如今的大学校园中&#xff0c;伴随着学生的购买能力的提高和每年的升学和毕业&#xff0c;存在许多…

调用阿里云API实现证件照生成

目录 1. 作者介绍2. 算法介绍2.1 阿里云介绍2.2 证件照生成背景2.3 图像分割算法 3.调用阿里云API进行证件照生成实例3.1 准备工作3.2 实验代码3.3 实验结果与分析 参考&#xff08;可供参考的链接和引用文献&#xff09; 1. 作者介绍 王逸腾&#xff0c;男&#xff0c;西安工…

ASEMI代理英飞凌TLD5097EL:理解和使用LED驱动器的综合指南

编辑-Z TLD5097EL是一款创新的LED驱动器&#xff0c;在照明行业掀起了波澜。这项先进的技术提供了广泛的好处&#xff0c;包括提高能源效率、延长使用寿命和增强性能。在本综合指南中&#xff0c;我们将探讨TLD5097EL的功能和优点&#xff0c;并提供如何有效利用该LED驱动器优…

python -- 绘制colorbar时设置标签为居中显示

python – 绘制colorbar时设置标签为居中显示 在海洋气象领域的相关研究中&#xff0c;对于一些异常信号的二维填色图绘制时&#xff0c;通常在设置colorbar都是以0为中心对称分布的。而在绘制colorbar时&#xff0c;由于存在负号会使得默认colorbar标签不太好看&#xff08;强…

window服务器环境将springboot jar包安装成一个window服务自启动

目录 1.下载WinSW工具 下载winswhttps://github.com/winsw/winsw/releases 2.新建一个Window Service信息的xml文件 3.将xml和exe重命名 4.安装卸载服务 5.修改配置文件 6.常用命令(注意winsw是exe名字 1.下载WinSW工具 下载winswhttps://github.com/winsw/winsw/rele…

图像降噪网络:KBNet 论文笔记

0 前言 Zhang Y, Li D, Shi X, et al. KBNet: Kernel Basis Network for Image Restoration[J]. arXiv preprint arXiv:2303.02881, 2023. https://arxiv.org/abs/2303.02881 论文主要提出了 Kernel Basis Attention Module 注意力模块&#xff0c;称为 KBA 模块。该模块可以轻…

[中阳期货】端午都有哪些习俗,为什么不能说快乐?

端午节&#xff08;屈原故里端午习俗&#xff09;&#xff0c;流行于湖北省宜昌市、秭归县的传统民俗&#xff0c;国家级非物质文化遗产之一。 “五月五&#xff08;农历&#xff09;&#xff0c;过端午。”端午节是中华民族的传统节日。《续齐谐记》、《荆楚岁时记》载&#x…

AI 人工智能介绍(一)

人工智能&#xff08;AI&#xff09;是一种利用计算机程序和算法来模拟人类智能的技术。通俗地说&#xff0c;就是让计算机能够像人一样思考、学习、推理和决策。 人工智能改变了我们的生活&#xff01;它被广泛应用于语音识别、计算机视觉、自然语言处理、智能机器人等多种领…

ubuntu20.04虚拟机安装

下载对应版本镜像文件&#xff08;iso&#xff09; 下载链接&#xff1a;https://releases.ubuntu.com/jammy/ 虚拟机安装工具为VMware 这里我的版本为下图所示 使用vmware创建虚拟机 1&#xff0c;点击 “创建新的虚拟机” 2&#xff0c;进入向导&#xff0c;选择自定义 …

基于docker部署的Selenium Grid分布式自动化测试

01、什么是Selenium Grid Selenium Grid是Selenium套件的一部分&#xff0c;它专门用于并行运行多个测试用例在不同的浏览器、操作系统和机器上。 Selenium Grid有两个版本——老版本Grid 1和新版本Grid 2。我们只对新版本做介绍&#xff0c;因为Selenium团队已经逐渐遗弃老版…

非线性规划求解方法:序列线性规划(Sequential linear programming)

来源&#xff1a;Cornell University Computational Optimization Open Textbook&#xff1a;SLP​​​​​​​ 目录 1.介绍 2.理论和方法 2.1 问题形式 2.1.1 NLP问题形式 2.1.2 SLP问题形式 2.2 步长边界 Step Bounds 2.3 完整的SLP算法 3.案例 3.1 example1 3.2…

数据结构:树状数组详解

一. 背景 那么我们为什么要用树状数组呢? 在解决一些区间求和的问题中 , 简单描述就是&#xff0c;对于一个给定的数组A&#xff0c;希望能够设计一个update函数来修改其中一个数的值&#xff0c;然后再设计一个sum函数来计算数组下标再给定参数l和r之间的值之和。关键点在于…

Docker Swarm 集群搭建和使用 —— 筑梦之路

简单介绍 swarm 集群由管理节点&#xff08;Manager&#xff09;和工作节点&#xff08;Worker&#xff09;构成。 管理节点&#xff1a;主要负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作。诸如监控集群状态、分发任务至工作节点等操作。 工作节点&…

【计算机视觉】使用 notebook 展示如何下载和运行 CLIP models,计算图片和文本相似度,实现 zero-shot 图片分类

文章目录 一、CLIP 模型二、准备三、加载模型四、查看图片处理器五、文本分词六、输入图片和文本&#xff0c;并可视化七、将图片和文字 encode 生成特征八、计算 cosine 相似度九、零样本进行图片分类十、编写函数进行图片分类十一、测试自己的函数十二、编写函数对多图片进行…

面对职业发展“迷茫期”除了抱怨焦虑我们还能做什么?

关注“软件测试藏经阁”微信公众号&#xff0c;回复暗号【软件测试】&#xff0c;即可获取氪肝整理的全套测试资源 Java和Python做自动化测试&#xff0c;哪个更有优势&#xff1f;这两个语言都是很流行的语言&#xff0c;所以从技术上很难说谁好谁不好的。因为要说好不好得看…

linux安装homeassistant(智能设备远程控制开源框架)

1、安装docker 先切换到root 用户&#xff0c;先安装一些基本环境&#xff1a; yum install -y yum-utils device-mapper-persistent-data lvm2添加阿里云软件源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo然后安装 D…

QT+OpenGL高级光照 Blinn-Phong和Gamma校正

QTOpenGL高级光照1 本篇完整工程见gitee:QtOpenGL 对应点的tag&#xff0c;由turbolove提供技术支持&#xff0c;您可以关注博主或者私信博主 Blinn-Phong 冯氏光照&#xff1a;视线与反射方向之间的夹角不小于90度&#xff0c;镜面光分量会变成0.0&#xff08;不是很合理&am…

死信队列小结

死信队列是RabbitMQ中非常重要的一个特性。简单理解&#xff0c;他是RabbitMQ对于未能正常消费的消息进行的 一种补救机制。死信队列也是一个普通的队列&#xff0c;同样可以在队列上声明消费者&#xff0c;继续对消息进行消费处理。 对于死信队列&#xff0c;在RabbitMQ中主要…

Spring 是什么?IoC 和 DI的区别

1. Spring 是什么?2. IoC是什么&#xff1f; 2.DI概念说明 1. Spring 是什么? 我们通常讲的Spring指的是Spring Framework(Spring框架),它是一个开源的框架,有着活跃而庞大的社区,这也是它之所谓经久不衰的原因。官方的解读是:Spring官网 翻译过来就是:Spring使Java编程对每…

学会这5个步骤,就能轻轻松松地获取代码覆盖率报告

目录 前言&#xff1a; 1、创建main函数的test文件 2、插桩方式编译源码 3、运行主服务 4、执行测试用例 5、优雅退出主服务&#xff0c;并生成覆盖率报告 前言&#xff1a; 代码覆盖率报告可以帮助我们了解测试用例的质量和覆盖程度。 小编前期所测项目多为go语言研发&…