Spring04_Aop

一、AOP 概述

(一)AOP简介

​ 面向切面编程是一种通过横切关注点(Cross-cutting Concerns)分离来增强代码模块性 的方法,它能够在不修改业务主体代码的情况下,对它添加额外的行为。

(二)为何需要AOP

​ 面向对象编程 OOP 可以通过对业务的分析,然后抽象出一系列具有一定属性与行为的类,并通过这些类之间的 协作来形成一个完整的软件功能。由于类可以继承,因此可以把具有相同功能或相同特性的属性抽象到一个层次分明 的类结构体系中。但是随着功能越来越复杂,难免暴露出一些问题。

​ 假设下面三个代码块A、B、C 中都有相同的部分 a,这个 a 通常是以类似复制粘贴的形式出现在这三个部分之中,但是一旦我们需要修改 a 的功能,就必须得把所有用到 a 的地方都修改一遍。

image-20230413203106986

​ 如果我们把 a 这部分设计成类似于 “方法” 的东西,再用得到他的地方进行 “调用” ,一旦需要修改我们只需要修改 a 这一个部分就可以,显然是更利于后期维护的。

但是在开发过程中甲方可能会不断的提要求,然后就得不断在 A、B、C 中增加新的逻辑,照上面说的那样我们就得写一个方法放在 “外面” ,然后再去 A、B、C 中去添加调用。

而 AOP 的设计思想就是只要我要增加新的方法,不需要在 A、B、C 中进行显示的调用,系统会自动在 A、B、C 中进行调用这个新的方法。

(三)AOP 名词

​ 切面(Aspect):被加入要执行的额外的新代码块。即切点和增强的组合

​ 连接点(Joinpoint):程序执行过程中明确的接入点,比如方法的调用,或者异常的抛出。在 Spring AOP 中,一个可以访问的方法就代表一个连接点。即可以进行增强的方法

​ 增强处理(Advice):AOP 框架在特定的切入点上执行的增强处理。增强处理有“around”、“before”、 “after”等类型。所谓的增强处理:就是在原代码不改动的情况下,额外多执行的那些代码。 即增加的额外功能

​ 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。

​ 引入:将方法或字段添加到被处理的类中。也就是说将增强作用于切点的过程

​ 目标对象:被 AOP 框架进行增强处理的对象,也被称为增强对象。如果 AOP 框架采用的是动态 AOP 实现, 那么该对象就是一个被代理的对象。即真实对象

​ AOP 代理:AOP 框架创建的对象,简单地说,代理就是对目标对象的增强。即代理对象

​ 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象的过程就是织入。织入有两种方式:编译时增强和运行时增强。(对应两种代理方式)

二、AOP 注解开发案例

​ 注解开发需要引入 AspectJ 的支持,需要将这个@Aspect 注解加入到 Spring 容器的过滤器中,那么扫描 包时才会将使用@Aspect 的 Bean 加入到 Spring 的容器中进行管理。

​ Spring1.X 版本采用自身提供的 AOP API 来定义切入点和增强处理,但目前这种方式已经过时。现在通常建议使用 AspectJ 方法来定义切入点和增强处理,也就是让 Spring 框架整合 AspectJ 框架,在这种方式下,Spring 依然有 “零配置” 和 “基于XML配置” 两种。

copy
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <!-- 启动对 AspectJ 的支持 --> <aop:aspectj-autoproxy /> <!--扫描包--> <context:component-scan base-package=""> <!-- 使用 Spring 在自动扫描组件,需要让 Spring 容器识别 AspectJ 的注解,因此将 AspectJ 框架 的 org.aspectj.lang.annotation.Aspect 注解类加入到 Spring 的过滤器中 --> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/> </context:component-scan>

(一)Before After 增强

​ 使用两个类来模拟用户注册时进行增强处理

copy
public interface UserService { public void regist(); } @Service("userService") public class UserServiceImpl implements UserService { @Override public void regist() { System.out.println("用户注册"); } } @Aspect public class UserServiceAspect { /** * 增强方法 */ @Before("execution(* com.qlu.service.impl.UserServiceImpl.*(..))") public void doBefore() { System.out.println("doBefore"); } }

​ MD,不要在这时候用实现类来创建接口,不然会报错 org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ' xxx.xxx.xx.xxx ' available。可以看这边解释,简单来说就是 Sping 在进行默认代理的时候是用的 JDK 代理,对实现类对象做增强得到的增强类和实现类是兄弟关系,所以不能用实现类接收增强类对象。当让你可以强制使用 CGLIB 就没事了。

copy
@Test public void testAop() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); //UserServiceImpl userService = applicationContext.getBean(UserServiceImpl.class); UserService userService = applicationContext.getBean(UserService.class); userService.regist(); }

image-20230415165055254

​ 可以更改注解的参数值来达成对什么方法进行增强,以及之前还是之后增强即前置增强和后置增强。

标识符 含义
execution() 表达式的主体
第一个 " * " 号 表示返回值是任意类型
com.qlu.service.impl.UserServiceImpl AOP所切的服务的包名,即需要进行横切的业务类
包名后面的“..” 表示当前包及子包
第二个 " * " 表示类名,*即所有类
.*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
copy
//作用于 com.qlu.service.impl.UserServiceImpl 类 @Before("execution(* com.qlu.service.impl.UserServiceImpl.*(..))") //作用于 com.qlu.service.impl 包及其子包的所有类型方法 @After("execution(* com.qlu.service.impl..*(..))")

(二)AfterReturning 和 AfterThrowing

​ 使用 @After 进行增强即使执行切点的原来方法有异常抛出,后置增强依旧会执行,如果使用 @AfterReturning 则会被异常阻止;

image-20230415195013000

​ 如果程序不出意外的话这两个注解的效果是一样的;

image-20230415195247800

​ 另外如果我们想把这个异常捕获处理的话可以使用 @AfterThrowing 注解。

copy
/** 对异常的处理 */ @AfterThrowing(value = "execution(* com.qlu.service.impl.UserServiceImpl.*(..))",throwing="ex") public void handleException(Throwable ex) { System.out.println("异常信息:"+ex.getMessage()); System.out.println("---------------将异常信息记录到数据库或日志文件中---------------"); }

image-20230415195658649

(三)Around 增强

​ 可以使用 @Around 进行环绕增强,顾名思义就是在切点的前后都进行增强。需要借助于 ProceedingJoinPoint 这个类,方法的参数是一个切点,通过 point.getArgs() 获得切点的参数,point.proceed(args) 执行切点的原本方法。

copy
@Around("execution(* com.qlu.service.impl.UserServiceImpl.*(..))") public void doAround(ProceedingJoinPoint point) throws Throwable { System.out.println("环绕增强执行之前"); Object[] args = point.getArgs(); Object result = point.proceed(args); System.out.println(result); System.out.println("环绕增强执行之后"); }

image-20230415194019935

(四)执行的先后顺序

​ Spring AOP 采用和 AspectJ 一样的优先顺序来织入增强处理:在“进入”连接点时,具有最高优先级的增强处理将先被织入,在“退出”连接点时,具有最高优先级的增强处理会最后被织入。

​ 当不同切面里的两个相同增强处理需要在同一个接入点被织入时,Spring AOP 将以随机的顺序来织入这两个增强处理。

​ 如果应用需要指定不同切面类里增强处理的优先级,Spring 提供了如下两个解决方案

​ 1、让切面类实现 org.springframework.core.Ordered 接口,该接口声明了一个 int getOrder()方法,该方法的返回值越小,则优先级越高。

​ 2、直接使用@Order 注解来修饰一个切面类,该注解中含有一个 int 类型的 value 属性,该属性值越小,优先级越高。

三、AOP XML开发

​ 除了使用 Annotation 注解方式来定义切面、切入点和增强处理外,Spring AOP 也允许直接使用 XML 配置文件来定义管理它们。使用 XML 形式的开发需要理解切点、切面、连接点这几个概念。下面使用一个简单案例模拟 Aop 的 xml 形式开发。

​ 用到的有 service 接口和他的实现类、aop 增强类、和用于测试的 junit4 生成的

copy
package com.qlu.service; public interface BookService { void add(); void delete(); void update(); void find(); } package com.qlu.service.impl; import com.qlu.service.BookService; public class BookServiceImpl implements BookService { @Override public void add() { System.out.println("增加"); } @Override public void delete() { System.out.println("删除"); } @Override public void update() { System.out.println("更新"); } @Override public void find() { System.out.println("查找"); } } //增强类使用hutool工具包来获取的当前时间 package com.qlu.aop; import cn.hutool.core.date.DateUtil; /** * 增强类 */ public class BookServiceAop { public void doBefore() { System.out.println(DateUtil.now()); } public Object doAround(ProceedingJoinPoint point) throws Throwable { System.out.println("Before " + DateUtil.now()); Object result = point.proceed(point.getArgs()); System.out.println("After " + DateUtil.now()); return result; } }

​ 重点是下面的 xml 文件配置

​ 首先要使用 spring 框架那肯定得把对象放到 ioc 容器里面,考虑到 aop 用的 xml 开发,索性配置 bean 也直接用 xml了。

​ 然后进行 aop-config ,里面有两个标签,一个是 aop-pointcut,也就是说你让谁成为切点,即想对哪个方法进行加强;

​ 另一个是aop-aspect 也就是切面,标签内使用 ref 指向编写的增强类,这样才能让 spring 找到增强类里面的方法;

​ aop-aspect里面放的是你写的增强方法,具体的 before、after、around 由自己配置,pointcut-ref 就是要把这个增强作用到哪个切点。

copy
<bean id="bookService" class="com.qlu.service.impl.BookServiceImpl"></bean> <bean id="bookServiceAop" class="com.qlu.aop.BookServiceAop"></bean> <aop:config> <!--定义切点--> <!-- <aop:before method="doBefore" pointcut-ref="bookServicePointcut"></aop:before>--> <aop:around method="doAround" pointcut-ref="bookServicePointcut"></aop:around> <!--切面--> <aop:aspect ref="bookServiceAop"> <aop:before method="doBefore" pointcut-ref="bookServicePointcut"></aop:before> </aop:aspect> </aop:config>

image-20230416165642398

image-20230416165720731

​ 自然其他注解支持的 xml 肯定也支持,就不做过多演示了。

posted @   Purearc  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
🚀