当前位置: 首页 > news >正文

ssm框架之spring:浅聊AOP

AOP(Aspect Oriented Programming),是一种设计思想。先看一下百度百科的解释:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

可以看出AOP是一种设计思想,也就是面向切面,它是面向对象编程的一种补充和完善,它是i同预编译方式,和运行期间通过动态代理实现再不修改源代码的情况下,添加额外功能的一种技术。

如不太了解代理模式,可以看另一类23模式–代理模式

概念了解

再演示AOP之前,应该先了解一些相关的疏于。 比如切面,通知等。

横切关注点

先看一下百度百科的解释:

横切关注点指的是一些具有横越多个模块的行为,使用传统的软件开发方法不能够达到有效的模块化的一类特殊关注点。在‘面向切面’软件开发中,横切关系是程序中和其他模块有联系的‘切面’。这些关系在程序模块化的设计和实现中无法被自然地分解到模块中,导致或代码过于分散,或代码冲突,或者两者都有。举个例子来说,编写一个处理医生记录的app,这些记录的索引是核心模块,同时关于存储记录或用户信息的数据的历史日志,或者登录的验证系统,由于和app中大部分模块都有关系所以成为了‘横切关系’。

其实很简单理解,就是在多个代码块中,有有一些代码会在多个模块中出现,它们就被成为横切关注点。

举个经典例子就是日志功能,日志公共往往横跨系统中的每个业务模块,也就是横切所有需要日志功能的方法或者类。所以称为日志功能成为横切整个系统的关注点,即日志公共可能称为横切关注点

日志功能的代码可以抽取处理,其非当前模块的核心业务,当然项目中不一定只有一个横切关注点。不过横切关注点不是语法层次的天然存在,而是根据附加功能逻辑上的需要。

通知

前面知道了横切关注点,同时可以将其共拥有的代码提出出来。当然每一个横切关注点都需要写一个方法来实现,整个方法就是通知方法。

也就是例子中的日志记录代码通过一个方法实现,而不是在所有的使用日志代码的模块中写一遍。而整个实现日志记录功能的方法就叫做通知方法

前面说过AOP用到了代理模式,所以这个通知方法,放置的位置不同,而通知方法得到的通知也有如下分类:

  • 前置通知:在被代理的目标方法前执行。
  • 返回通知:在被代理的目标方法成功完成后执行。
  • 异常通知:在被代理的目标方法异常完成后执行。
  • 后置通知:在被代理的目标方法无论成功完成或异常完成后执行。
  • 环绕通知:使用try-catch-finally结构环绕整个被代理的目标方法,当然包括上面四种通知对应的所有位置。

其实通知的执行顺序还有有不同的:

  • 在5.3版本之前: 前置通知-------->目标操作-------->后置通知-------->返回通知或者异常通知。
  • 在5.3版本之后: 前置通知–》目标操作-------->返回通知或者异常通知-------->后置通知。

切面

前面既然提取好通知方法了,将其单独进行封闭,方便有需要使用这通知方法模板使用。而这种封装通知方法的类,被称为切面或者说是切面类

目标(被代理对象)和代理

一般被代理的对象就是目标对象,比如哪些核心模块需要拥有日志功能,而这核心模块就是目标。

通过代理模式而给目标对象扩展新功能,也就是调用切面类的通知方法的类,就是代理对象。

连接点

这也是一个纯逻辑概念,不是语法定义的,简单说就是可以被拦截的点

因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。

也就是在spring中目标类的方法都可以说都有连接点,毕竟都可以被拦截。

切入点

前面连点说到,比如在spring中目标类的方法都可以说是连接点。但是对于开发者来说这些连接点不都是需要进行拦截扩展功能,而需要自己通过条件进行定位某个连接点。

简单的说里面有共性功能的方法(在提取这些非核心共有的代码之前)被称之为切入点。

所以整个就涉及到连接点和切入点的区别:连接点指所有的方法,切入点仅指那些有共享功能的方法。

织入

毕竟提取了共有部分,还是需要其生效的,所以找到了切入点,将提取这些非核心共有的代码回填的动态过程,叫做织入。

通过了解AOP中的一些概念可以看出AOP的作用:

  • 简化代码:把方法中固定位置的重复共有代码抽取出来,让被抽取的方法,专注于自己的核心功能,提高了内聚性。同时也减少了代码的重复。
  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了.

代码演示–通过注解

代码演示通过AspectJ这个个面向切面的框架。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

既然需要这个框架,既然是需要导入这个jar包的,在spring再导入一个spring-aspects-*.*.*.jar 。

因为通过maven,所以直接再pom.xml中配置依赖即可。

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.24</version>
</dependency>

然后肯定再spring配置文件中进行扫描,为了方便我们直接这样配置:

    <context:component-scan base-package="com.xzd.aoptest" ></context:component-scan>

同时需要开启在xml头部添加:xmlns:aop=“http://www.springframework.org/schema/aop”

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
"><context:component-scan base-package="com.xzd.aoptest" ></context:component-scan>
<!--    开启aop注解功能--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

还需了解一个注解:@Aspect,这个注解放在切面类上面,告诉jvm虚拟机来说这个是一个切面类,具体如何使用,可以在前提中的切面类中查看。

前面说到了几种通知,当然AspectJ自然有对应的注解:

注解描述
@Before前置通知,也就是在被代理的目标方法前执行。
@After后置通知,在被代理的目标方法无论成功完成或异常完成后执行。
@AfterReturning返回通知,在被代理的目标方法成功完成后执行。
@AfterThrowing异常通知,在被代理的目标方法异常完成后执行。
@Around环绕通知,使用try-catch-finally结构环绕整个被代理的目标方法,当然包括上面四种通知对应的所有位置。

前提

创建一个接口,一个实现接口类,还有一个就是切面类。

接口:

public interface Calculate {int add(int a,int b);int sub(int a,int b);int mul(int a,int b);int div(int a,int b);
}

实现接口类:

// 只是简单的印证而已,所以传入的数据,都已满足不进行数据转换了
// 因为使用注解,所以通过注解实现bean的创建
@Component
public class CalculateImpl implements Calculate {@Overridepublic int add(int a, int b) {int i= a+b;System.out.println("被代理目标执行的add");return i;}@Overridepublic int sub(int a, int b) {int i= a-b;System.out.println("被代理目标执行的sub");return i;}@Overridepublic int mul(int a, int b) {int i= a*b;System.out.println("被代理目标执行的mul");return i;}@Overridepublic int div(int a, int b) {int i= a/b;System.out.println("被代理目标执行的div");return i;}
}

先提炼出来一个切面类(记得在切面类上添加注解@Aspect,提示这个切面类):

//这是一个切面类 通过注解实现创建对象bean
@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {}

测试类:

public class testaop {@Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");}
}

前置通知–@Before

看一下其属性:

@Before(value= "切入点表达式或命名切入点",argNames = "指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔")

先来一个小例子,然后聊:

//这是一个切面类 通过注解实现创建对象bean
@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {@Before("execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int))")public void beforeMethod(){System.out.println("调用目标的方法之前调用");}}

其实上面的注解省略 ,全写法是:

@Before(value = "execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int))")

很多注解中value都是可以省略的,所以一般使用的时候为了方便使用,直接省略掉value=

这个可以看出在@Before中有一个execution关键字,不要问为什么这样写。很简单,因为是文档要求,先看一下运行结果。

    @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.add(1,2));}

在这里插入图片描述

没有问题,可以实现了前置通知,这个时候需要具体聊一下关键字:execution

execution

这个其实就是描述了需要连接点中的具体切入点是哪里,比如上面切入点是add方法:

 @Before("execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int))")

为什么在写切入点的社会需要将方法修饰范围,以及返回值,同时还有方法对象的类路径,参数都需要写详细呢?

因为java中在不同的包下可以有相同类,在不同的类中可能有相同的方法,就算是同一个类中也有方法重载,所以需要写的具体信息来确定切入点方法。

execution还可以搭配特殊符号使用,有些类似正则表达式。现在开始进行简单的演示:

例子1:

 @Before("execution(public  int com.xzd.aoptest.CalculateImpl.add(..))")

这个表示在这个类下通过public int 修饰的所有add方法,无论多少个参数。这个 (…) 表示任意参数的类表。

例子2:

 @Before("execution( * com.xzd.aoptest.CalculateImpl.add(..))")

这个将修复方法public int替换为*****。这位置的*表示任意访问修饰符和返回类型

例子3:

// 可以结合使用,其中*位置不同表示不同
@Before("execution( * com.xzd.aoptest.*.*(..))")

这个位置可以看出有三个***,第一个*例子2中已经说明,现在说一下后面两个***的意义。

  • 第二个****出现在类的地方,表示该包下的所有类,当然也可以出现在包路径中某个包名用*,表示此包路径下所有的类。**
  • 第三个***:*出现在方法的地方,表示该类下的所有方法**

现在通过例子进行演示:

//这是一个切面类 通过注解实现创建对象bean
@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {@Before("execution(public  int com.xzd.*.*.*(..))")public void beforeMethod(){System.out.println("调用目标的方法之前调用");}}

然后调用:

  @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.add(1,2));System.out.println(calculate.div(10,2));System.out.println(calculate.sub(6,2));System.out.println(calculate.mul(6,2));}

看一下结果:

在这里插入图片描述

argNames

如果不写argNames直接为方法添加参数会如下报错:

在这里插入图片描述

可以看出会报错。但是对于加强方法是否可以带参数,以及形参的意义是什么?

但是如下写:

  @Before(value = "execution(* com.xzd.aoptest.CalculateImpl.add(int,int)) && args(c,d)" ,argNames = "c,d")
//     可以这样写  可以没有,argNames = "c,d" 但是需有 && args(c,d)
//    @Before(value = "execution(public  int com.xzd.*.*.*(..)) && args(c,d)")public void beforeMethod(){System.out.println("调用目标的方法之前调用");}

在这里插入图片描述

可以看出如果使用了**argNames = “c,d”(也就是&& args(c,d) ,这个表示的是切入点方法传到前置通知方法中的形参是c,d)**那么增强的方法必须带有参数不然就会报错。但是argNames 可以将传递参数的位置变一下,比如这样:argNames = “d,c”,而且前置通过方法必须对应的名字是(int d,int c)表示前置通知方法中第一个参数是切入点方法的第二个参数。第二个参数是切入点方法的第一个参数。当然默认是和args顺序一样。

**简单说就是:&& args 决定了通知方法中得到切入点方法后的形参,而argNames 决定了在通知方法中形参对应的顺序。默认argNames是和&& args 顺序一样的 **

那么先和要增强的方法参数类型和个数一样试试:

    @Before(value = "execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int)) && args(c,d) ",argNames = "c,d")
//     可以这样写  可以没有,argNames = "c,d" 但是需有 && args(c,d)
//    @Before(value = "execution(public  int com.xzd.*.*.*(..)) && args(c,d)")public void beforeMethod(int c,int d){System.out.println("调用之前的方法的参数 c="+c+"  d= "+d);System.out.println("调用目标的方法之前调用");}

然后看一下结果:

在这里插入图片描述

可以看出参数也就是切入点传递的参数,如果再大胆一些,修改一个参数类型是其它类型试试:

    @Before(value = "execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int)) && args(c,d) ",argNames = "c,d")
//     可以这样写  可以没有,argNames = "c,d" 但是需有 && args(c,d)
//    @Before(value = "execution(public  int com.xzd.*.*.*(..)) && args(c,d)")public void beforeMethod(int c,String d){System.out.println("调用之前的方法的参数 c="+c+"  d= "+d);System.out.println("调用目标的方法之前调用");}

看一下结果:

在这里插入图片描述

然后又做了一个一个实验,那就是传递的参数不同也是这样,前置通知没有效果,从这里可以看出对于前置通知注解 @Before:如果不使用argNames那么不带参数,满足切入点的方法都会调用,如果使用了 argNames那么不但要满足好切入点方法,而且增强的方法也必须和切入点方法的参数个数和依次类型必须一致,不然没有效果(参数如果是JoinPoint例外)。而且参数在值就是切入点方法传入值。

带JoinPoint这样写:

  @Before(value = "execution(* com.xzd.aoptest.CalculateImpl.add(int,int)) && args(c,d)" ,argNames = "joinPoint,c,d")public void beforeMethod(JoinPoint joinPoint,int c ,int d){System.out.println("调用目标的方法之前调用");}

在这里插入图片描述

JoinPoint具体是什么下面会聊,不再这里聊了。

补充 aop无法得到目标对象

使用AOP之后,其实是无法得到目标对象的,比如:

    @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类 如果返回实现接口的类CalculateImpl ccalculateImpl= (CalculateImpl) applicationContext.getBean("calculateImpl");}

在这里插入图片描述

可以看出使用了AOP之后,无法直接获取被代理的对象了。

因为使用了动态代理,所以返回的应该是代理对象,而不是被代理对象。

补充----JoinPoint

如果在通知方法中放入这个参数,如下不会报错:

   @Before(value = "execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int))) " )public void beforeMethod(JoinPoint point){System.out.println("调用目标的方法之前调用");}

看一下这个接口源码解释:

在这里插入图片描述

可以看出:

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象. 其包含了太多信息,现在进行演示:

    @Before(value = "execution(public  int com.xzd.aoptest.CalculateImpl.add(int,int))) " )public void beforeMethod(JoinPoint point){System.out.println("JoinPoint 测试方法开始--------");System.out.println("getSignature ==="+point.getSignature());System.out.println("getKind ==="+point.getKind());System.out.println("getArgs ==="+Arrays.toString(point.getArgs()));System.out.println("getTarget ==="+point.getTarget());System.out.println("getSourceLocation ==="+point.getSourceLocation());System.out.println("getClass ==="+point.getClass());System.out.println("JoinPoint 测试方法结束--------");System.out.println("调用目标的方法之前调用");}

看下结果:

在这里插入图片描述

如果要结合带参数一起使用如下:

   @Before(value = "execution(* com.xzd.aoptest.CalculateImpl.add(int,int)) && args(c,d)" ,argNames = "joinPoint,c,d")
//  参数joinPoint所在位置没关系,必须一一于argNames对应,而且argNames的形参名字也需要一致public void beforeMethod(JoinPoint joinPoint,int c ,int d){System.out.println("调用目标的方法之前调用");}

参数joinPoint所在位置必须在第一个位置,而且argNames的形参名字也需要一致。虽然没有看源码为什么必须在首位,但是想到了肯定是通过反射得到了以不定项参数方法生成形参,所以第一个位置默认定义好。

后置通知—@After

格式:

@After(value= "切入点表达式或命名切入点",argNames = "指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔")

这个使用和前置通知使用很相似,所以直接演示:

   @After("execution(public  int com.xzd.*.*.*(..))")public  void afterMethod(){System.out.println("调用目标的方法之后调用");}

然后调用:

  @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.add(1,2));System.out.println(calculate.div(10,2));System.out.println(calculate.sub(6,2));System.out.println(calculate.mul(6,2));}

在这里插入图片描述

也使用execution等关键字,这个就不再重复了。而且@After的后置通知,是无论是异常退出还是正常推出都会被执行。

比如调用除法:

    @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.div(1,0));}

在这里插入图片描述

返回通知----@AfterReturning

这个需要说一下,其有注解中的属性值会多一些:

//pointcut与value是一样的。如果使用pointcut来声明,那么前面声明的value就没用了。
@AfterReturning(
value="切入点表达式或命名切入点",//pointcut="切入点表达式或命名切入点",argNames="参数列表参数名",returning="目标对象的返回值"
)

先来一个简单例子:

    @AfterReturning(value = "execution(public  int com.xzd.*.*.*(..))")public void afterReturnMethod(){System.out.println("返回通知");}

看一下结果:

在这里插入图片描述

这样写似乎没有问题,但是有可能会有这样一个需求,那就是切入点的返回值,也许在通知中取到,这个时候应该如何。

如下操作:

//    @AfterReturning(value = "execution(public  int com.xzd.*.*.*(..)) && args(c,d)",returning = "addresulet")
//   如果不使用  argNames  默认是顺序是int c,int d, int addresulet)
//   (int c,int d, int addresulet)@AfterReturning(value = "execution(public  int com.xzd.*.*.*(..)) && args(c,d)",returning = "addresulet",argNames = "c,addresulet,d")public void afterReturnMethod(int c, int addresulet,int d){System.out.println("addresulet ===="+addresulet);System.out.println("返回通知");}

异常通知 ----@AfterThrowing

//pointcut与value是一样的。如果使用pointcut来声明,那么前面声明的value就没用了。
@AfterThrowing(value="切入点表达式或命名切入点",//pointcut="切入点表达式或命名切入点",argNames="参数列表参数名",throwing="异常对应参数名")

很多参数类似,所以不再重复演示,演示其中一个即可。

   @AfterThrowing(value = "execution(public  int com.xzd.*.*.*(..))",throwing = "throwname")public  void  affterThrowing(Throwable throwname){System.out.println("异常通知"+throwname);}

如果正常调用:

   @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.div(1,1));}

可以看出结果,没有异常通知:

在这里插入图片描述

但是估计来一个错误的:

 @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println(calculate.div(1,0));}

在这里插入图片描述

环绕通知—@Around

@Around( value="切入点表达式或命名切入点",argNames="参数列表参数名")

这样看似乎最少的参数传递,这个涉及到另一个类:ProceedingJoinPoint

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中使用。

ProceedingJoinPoint自然会实现JoinPoint的方法,当然也在拓展了方法,其也有自己的方法,其中最常用的:proceed(),下面进行简单演示:

@Around("execution(public  int com.xzd.*.*.*(..))")public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 如何结合try-catch-finally 实现所有地方的通知try {System.out.println("切入点方法===="+proceedingJoinPoint.getSignature()+"   切入点方法参数"+Arrays.toString(proceedingJoinPoint.getArgs()));System.out.println("环绕通知-------------------前置通知");Object result=proceedingJoinPoint.proceed();System.out.println("环绕通知中proceed执行返回的result==="+result);result=6;System.out.println("环绕通知-----------------返回通知");return  result;} catch (Exception e) {System.out.println("环绕通知---------------异常通知"+e);throw new RuntimeException(e);} finally {System.out.println("环绕通知---------------后置通知");}}
}

然后通过调用:

  @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println("最后结果-----"+calculate.div(6,2));}

看一下结果:

在这里插入图片描述

然后再试一下异常通知:

  @Testpublic void  test1(){ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring_test.xml");//这里返回的是接口类Calculate calculate= (Calculate) applicationContext.getBean("calculateImpl");System.out.println("最后结果-----"+calculate.div(6,0));}

在这里插入图片描述

这个需要注意两点:

  • 在环绕通知中通过Object result=proceedingJoinPoint.proceed();表示切入点方法也就是目标方法的执行。

  • 环绕通知方法一般都有返回值,毕竟切入点方法人家有返回指定,所以无论是否有返回值都有返回值,而且两者返回值。因为这个环绕通知方法的return值才是目标方法执行的返回值,比如上面案例中6除以2明明是3,但是在调用的时候返回时6.所以不写proceed()也会有返回值,只是不会调用目标方法而已。

同一个类中环绕通知与其它四个通知的关系

@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {//    private String addreturn;@Before("execution(public  int com.xzd.*.*.*(..))")public void beforeMethod(){System.out.println(" @Before------调用目标的方法之前调用");}@After("execution(public  int com.xzd.*.*.*(..))")public  void afterMethod() throws Throwable {System.out.println("@After   调用目标的方法之后调用");}@AfterReturning(value = "execution(public  int com.xzd.*.*.*(..))",returning = "addresulet",argNames = "addresulet")public void afterReturnMethod(int addresulet ){System.out.println(" @AfterReturning     返回通知");}@AfterThrowing(value = "execution(public  int com.xzd.*.*.*(..))",throwing = "throwname")public  void  affterThrowing(Throwable throwname){System.out.println("@AfterThrowing      异常通知"+throwname);}
@Around("execution(public  int com.xzd.*.*.*(..))")public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {try {System.out.println("切入点方法===="+proceedingJoinPoint.getSignature()+"   切入点方法参数"+Arrays.toString(proceedingJoinPoint.getArgs()));System.out.println("环绕通知-------------------前置通知");Object result=proceedingJoinPoint.proceed();System.out.println("环绕通知-----------------返回通知");return  result;} catch (Exception e) {System.out.println("环绕通知---------------异常通知"+e);throw new RuntimeException(e);} finally {System.out.println("环绕通知---------------后置通知");}}
}

看一下结果:

在这里插入图片描述

可以看出似乎环绕通知中的前置通知,被直接用@Before的前置通知早以外,其它的都晚。

但是如果将环绕通知中的:

Object result=proceedingJoinPoint.proceed();
转换为
Object result=1;//proceedingJoinPoint.proceed();    

在这里插入图片描述

这个可以看出根本就没有调用@Before等其它通知,为什么?

因为环绕通知会将其它四个通知包裹主,但是如果不执行proceedingJoinPoint.proceed()方法,就不会在依次执行@Before等四个通知的方法。

补充其它几种常用注解

重写切入点表达式----@Pointcut

上面演示的时候,发现有些切入点是共有的,每次都需要写,有时候太麻烦了,所以有了这个注解,现在演示一下:

@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {@Pointcut("execution(public  int com.xzd.*.*.*(..))")public  void pointCutMethod(){}
//    private String addreturn;@Before("pointCutMethod()")public void beforeMethod(){System.out.println(" @Before------调用目标的方法之前调用");}@After("pointCutMethod()")public  void afterMethod() throws Throwable {System.out.println("@After   调用目标的方法之后调用");}
}

在这里插入图片描述

可以看出**@Pointcut**就是声明了一个公共的切入表达式,方便调用。

切面类的优先级-----@Order

这个有涉及到一个问题,那就是切面类有时候会创建多个,但是如果创建多个,那先执行谁?

还是第一个是日志切面类:

@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
public class LogAspects {@Pointcut("execution(public  int com.xzd.*.*.*(..))")public  void pointCutMethod(){}
//    private String addreturn;@Before("pointCutMethod()")public void beforeMethod(){System.out.println(" @Before------调用目标的方法之前调用");}
}

再创建一个验证切面类,毕竟输入的必须保证是整数。:

@Component
@Aspect
public class VerifyAspectj {@Pointcut("execution(public  int com.xzd.*.*.*(..))")public  void pointCutMethod(){}
//    不实现具体方法了,只是简单的演示@Before("pointCutMethod()")public void verifyMethod(){System.out.println(" @Before-----VerifyAspectj -调用目标的方法之前调用");}
}

然后看一下运行结果:

在这里插入图片描述

运行顺序和计划的有些出入,所以这个时候需要使用@Order这个注解。

在这里插入图片描述

所有的切面类中@Order默认值是最大整数,其数值越小表示优先级越高,所以可以如下修改:

@Component
//通过aspects框架注解,告诉系统这个是一个切面类
@Aspect
//因为就两个可以使用默认值
@Order(101)
public class LogAspects {@Pointcut("execution(public  int com.xzd.*.*.*(..))")public  void pointCutMethod(){}
//    private String addreturn;@Before("pointCutMethod()")public void beforeMethod(){System.out.println(" @Before------调用目标的方法之前调用");}
}
@Component
@Aspect
@Order(100)
public class VerifyAspectj {@Pointcut("execution(public  int com.xzd.*.*.*(..))")public  void pointCutMethod(){}
//    不实现具体方法了,只是简单的演示@Before("pointCutMethod()")public void verifyMethod(){System.out.println(" @Before-----VerifyAspectj -调用目标的方法之前调用");}
}

看一结果:

在这里插入图片描述

通配符与逻辑运算符

  • @Aspectj支持3种通配符:

    • * 匹配任意字符,但它只能匹配上下文中的一个元素
    • 匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用。
    • + 表示按类型匹配指定类及其子孙类,必须跟在类名后面。如cn.framelife.spring.UserService+表示UserService类及其子类。
  • 函数支持

    • 支持所有的通配符的函数:execution()、within()
    • 仅支持 + 通配符的函数:args()、this()、targ()。虽然这三个函数可以支持 + 通配符,但对于这些函数来说使用和不使用 + 都是一样的
    • 不支持通配符的函数:@args、@within、@target()、@annotation()。也就是所有用于注解上的函数都不支持通配符。
  • @Aspectj支持的逻辑运算符:

    • && :与
    • || : 或
    • ! :非

还有一些注解或者注解中的属性:within(),target(),this(),@annotation()等等就不在演示了。

代码演示–通过XML

配置xml

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"><context:component-scan base-package="com.xzd.aoptest"></context:component-scan><!--    用xml启动aop-->
<aop:config></aop:config>
</beans>

可以看下aop:config下的三个标签:

在这里插入图片描述

现在可以描述一下:

标签描述
aop:advisor配置通知,但是这个与事务有关,等聊事务的时候再说
aop:pointcut配置切入点表达式,优点@Pointcut注解的样子
aop:aspect配置切面类

然后先一个例子演示一下:

@Component
public class LogAopXML {public void beforeMethod(){System.out.println(" @Before-----LogAspects -调用目标的方法之前调用");}
}

然后配置XML:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"><context:component-scan base-package="com.xzd.aoptest"><!--    用xml其它aop-->
<aop:config>
<!--   因为通过注解实现注入,默认驼峰 也就是xml log类上有注解@Component-->
<aop:aspect ref="logAopXML"><aop:before method="beforeMethod" pointcut="execution(public  int com.xzd.*.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>

然后看一下运行结果:

在这里插入图片描述

前置通知---- <aop:before>

method: 配置的是切面类中的通知方法名字
pointcut 或者 pointcut-ref: 配置切入点的类 pointcut="execution(和注解配置一样)"  或者 pointcut-ref="设置的切入点表达式"
arg-names:配置通知方法的参数,这个需要结合execution(和注解配置一样)and args(d,c) 一起使用和注解要求一样不再过多解释

来一个完整xml配置:

 <aop:before method="beforeMethod" pointcut="execution(public  int com.xzd.*.*.*(..)) and args(d,c)" arg-names="c,d" ></aop:before>

不再进行演示了,因为和注解演示的很多了。当然也可以添加参数joinPoint,和注解中的要求是一致的。

返回通知—<aop:after-returning>

method: 配置的是切面类中的通知方法名字
pointcut 或者 pointcut-ref: 配置切入点的类 pointcut="execution(和注解配置一样)"  或者 pointcut-ref="设置的切入点表达式"
returning: 表示切入点返回的值,然后可以通过arg-names配置将其传递给返回通知方法
arg-names:配置通知方法的参数,这个需要结合execution(和注解配置一样)and args(d,c) 一起使用和注解要求一样不再过多解释

来一个配置:

    <aop:after-returning method="afterReturnMethod" pointcut="execution(public  int com.xzd.*.*.*(..))" returning="addresulet" arg-names="addresulet" ></aop:after-returning>

也不再演示结果,其要求和注解中对参数值的要求一致。

异常通知----<aop:after-throwing>

method: 配置的是切面类中的通知方法名字
pointcut 或者 pointcut-ref: 配置切入点的类 pointcut="execution(和注解配置一样)"  或者 pointcut-ref="设置的切入点表达式"
throwing: 表示切入点抛出的异常,然后可以通过arg-names配置将其传递给返回通知方法
arg-names:配置通知方法的参数,这个需要结合execution(和注解配置一样)and args(d,c) 一起使用和注解要求一样不再过多解释

来一个完整配置:

    <aop:after-throwing method="affterThrowing" pointcut="execution(public  int com.xzd.*.*.*(..))" throwing="thrromname"  arg-names="thrromname"></aop:after-throwing>

也不再演示结果,其要求和注解中对参数值的要求一致。

后置通知----<aop:after>

method: 配置的是切面类中的通知方法名字
pointcut 或者 pointcut-ref: 配置切入点的类 pointcut="execution(和注解配置一样)"  或者 pointcut-ref="设置的切入点表达式"
arg-names:配置通知方法的参数,这个需要结合execution(和注解配置一样)and args(d,c) 一起使用和注解要求一样不再过多解释

来一个完整例子:

 <aop:after method="beforeMethod" pointcut="execution(public  int com.xzd.*.*.*(..)) and args(d,c)" arg-names="c,d" ></aop:after>

也不再演示结果,其要求和注解中对参数值的要求一致。

环绕通知—<aop:around>

method: 配置的是切面类中的通知方法名字
pointcut 或者 pointcut-ref: 配置切入点的类 pointcut="execution(和注解配置一样)"  或者 pointcut-ref="设置的切入点表达式"
arg-names:配置通知方法的参数,这个需要结合execution(和注解配置一样)and args(d,c) 一起使用和注解要求一样不再过多解释

当然其也会用到ProceedingJoinPoint这个参数,和注解一样的。

来一个例子演示:

<aop:around method="aroundMethod" pointcut="execution(public  int com.xzd.*.*.*(..))"></aop:around>

也不再演示结果,其要求和注解中对参数值的要求一致。

对于xml配置AOP了解即可,一般的时候更侧重于用注解进行配置。

相关文章:

ssm框架之spring:浅聊AOP

AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff0c;是一种设计思想。先看一下百度百科的解释&#xff1a; 在软件业&#xff0c;AOP为Aspect Oriented Programming的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期间动态…...

k8s详解

一、k8s中的yaml文件 JSON格式&#xff1a;主要用于api接口之间信息的传递YAML格式&#xff1a;主要用于配置和管理&#xff0c;YAML是一种简洁的非标记性语言&#xff0c;内容格式人性化 YAML格式&#xff1a; 大小写敏感使用缩进代表层级关系&#xff0c;不支持TAB制表符缩…...

计算机操作系统(第四版)第一章操作系统引论 1.1操作系统的目标和作用

第一章操作系统引论 1.1操作系统的目标和作用 什么是操作系统OS&#xff1f; 配置在计算机硬件上的第一层软件是对硬件的首次扩充。 是最重要的系统软件&#xff0c;其他系统软件应用软件都依赖于操作系统的支持。 操作系统主要作用&#xff1f; 管理计算机系统所有硬件设…...

git push解决办法: ! [remote rejected] master -> master (pre-receive hook declined)

项目经理远程创建了一个空项目&#xff0c;无任何内容&#xff0c;给我赋予的developer账号权限&#xff0c;本地改为后提交代码试了很多次都上传不上去&#xff0c;报错如下&#xff1a; ! [remote rejected] master -> master (pre-receive hook declined)先说结果&#x…...

jQuery 遍历方法总结

遍历方法有&#xff1a;1、add()&#xff0c;用于把元素添加到匹配元素的集合中&#xff1b;2、children()&#xff0c;用于返回被选元素的所有直接子元素&#xff1b;3、closest()&#xff0c;用于返回被选元素的第一个祖先元素&#xff1b;4、contents()&#xff0c;用于返回…...

OKHttp 源码解析(二)拦截器

游戏SDK架构设计之代码实现——网络框架 OKHttp 源码解析&#xff08;一&#xff09; OKHttp 源码解析&#xff08;二&#xff09;拦截器 前言 上一篇解读了OKHttp 的基本框架源码&#xff0c;其中 OKHttp 发送请求的核心是调用 getResponseWithInterceptorChain 构建拦截器链…...

如何修改设置浏览器内核模式

优先级&#xff1a; 强制锁定极速模式 >手动切换&#xff08;用户&#xff09;>meta指定&#xff08;开发者&#xff09;>浏览器兼容列表&#xff08;浏览器&#xff09; 需要用360安全浏览器14&#xff0c;chromium108内核&#xff0c;下载地址https://bbs.360.cn/t…...

30个Python常用小技巧

1、原地交换两个数字 1 2 3 4 x, y 10, 20 print(x, y) y, x x, y print(x, y) 10 20 20 10 2、链状比较操作符 1 2 3 n 10 print(1 < n < 20) print(1 > n < 9) True False 3、使用三元操作符来实现条件赋值 [表达式为真的返回值] if [表达式] else [表达式…...

ubuntu解决中文乱码

1、查看当前系统使用的字符编码 ~$ locale LANGen_US LANGUAGEen_US: LC_CTYPE"en_US" LC_NUMERIC"en_US" LC_TIME"en_US" LC_COLLATE"en_US" LC_MONETARY"en_US" LC_MESSAGES"en_US" LC_PAPER"en_US" …...

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题——MYSQL安全测试解析(详细)

B-3任务三:MYSQL安全测试 *任务说明:仅能获取Server3的IP地址 1.利用渗透机场景kali中的工具确定MySQL的端口,将MySQL端口作为Flag值提交; 2.管理者曾在web界面登陆数据库,并执行了select <?php echo \<pre>\;system($_GET[\cmd\]); echo \</pre>\; ?…...

C++ map和unordered_map的区别

unordered_map 类模板和 map 类模板都是描述了这么一个对象&#xff1a;它是由 std::pair<const Key, value> 组成的可变长容器&#xff1b; 这个容器中每个元素存储两个对象&#xff0c;也就是 key - value 对。 1. unordered_map 在头文件上&#xff0c;引入 <unor…...

BCSP-玄子JAVA开发之JAVA数据库编程CH-04_SQL高级(二)

BCSP-玄子JAVA开发之JAVA数据库编程CH-04_SQL高级&#xff08;二&#xff09; 4.1 IN 4.1.1 IN 子查询 如果子查询的结果为多个值&#xff0c;就会导致代码报错解决方案就是使用 IN 关键字&#xff0c;将 替换成 IN SELECT …… FROM 表名 WHERE 字段名 IN (子查询);4.1.…...

学习java——②面向对象的三大特征

目录 面向对象的三大基本特征 封装 封装demo 继承 继承demo 多态 面向对象的三大基本特征 我们说面向对象的开发范式&#xff0c;其实是对现实世界的理解和抽象的方法&#xff0c;那么&#xff0c;具体如何将现实世界抽象成代码呢&#xff1f;这就需要运用到面向对象的三大…...

初阶数据结构 - 【单链表】

目录 前言&#xff1a; 1.概念 链表定义 结点结构体定义 结点的创建 2.链表的头插法 动画演示 代码实现 3.链表的尾插 动画演示 代码实现 4.链表的头删 动画演示 代码实现 5.链表的尾删 动画演示 代码实现 6.链表从中间插入结点 动画演示 代码实现 7.从单…...

第五周作业、第一次作业(1.5个小时)、练习一

一、创建servlet的过程没有太多好说的&#xff0c;唯一需要注意的就是&#xff1a;旧版本的servlet确实需要手动配置web.xml文件&#xff0c;但是servlet2.5以后,servlet的配置直接在Java代码中进行注解配置。我用的版本就不再需要手动去配置web.xml文件了&#xff0c;所以我只…...

【正点原子FPGA连载】 第三十三章基于lwip的tftp server实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

第三十三章基于lwip的tftp server实验 文件传输是网络环境中的一项基本应用&#xff0c;其作用是将一台电子设备中的文件传输到另一台可能相距很远的电子设备中。TFTP作为TCP/IP协议族中的一个用来在客户机与服务器之间进行文件传输的协议&#xff0c;常用于无盘工作站、路由器…...

蓝桥冲刺31天之316

如果生活突然向你发难 躲不过那就迎面而战 所谓无坚不摧 是能享受最好的&#xff0c;也能承受最坏的 大不了逢山开路&#xff0c;遇水搭桥 若你决定灿烂&#xff0c;山无遮&#xff0c;海无拦 A&#xff1a;特殊日期 问题描述 对于一个日期&#xff0c;我们可以计算出年份的各个…...

说一个通俗易懂的PLC工程师岗位要求

你到了一家新的单位&#xff0c;人家接了一套新的设备&#xff0c;在了解设备工艺流程之后&#xff0c;你就能决定用什么电气元件&#xff0c;至少95%以上电气原件不论你用过没用过都有把握拍板使用&#xff0c;剩下5%&#xff0c;3%你可以先买来做实验&#xff0c;这次不能用&…...

今年还能学java么?

“Java很卷”、“大家不要再卷Java了”&#xff0c;经常听到同学这样抱怨。但同时&#xff0c;Java的高薪也在吸引越来越多的同学。不少同学开始疑惑&#xff1a;既然Java这么卷&#xff0c;还值得我入行吗&#xff1f; 首先先给你吃一颗定心丸&#xff1a;现在选择Java依然有…...

ajax学习1

不刷新页面的情况下&#xff0c;向服务端发送请求&#xff0c;异步的js和XMLajax不是新的编程语言&#xff0c;只是把现有标准组合到一起使用的新方式...

一题多解-八数码(万字长文)

16 张炜皓 (ζ͡顾念̶) LV 5 1 周前 在做这道题前&#xff0c;先来认识一下deque双端队列 C STL 中的双端队列 题目连接 使用前需要先引入 头文件。 #include; STL 中对 deque 的定义 // clang-format off template< class T, class Allocator std::allocator class d…...

九种跨域方式实现原理(完整版)

前言 前后端数据交互经常会碰到请求跨域&#xff0c;什么是跨域&#xff0c;以及有哪几种跨域方式&#xff0c;这是本文要探讨的内容。 一、什么是跨域&#xff1f; 1.什么是同源策略及其限制内容&#xff1f; 同源策略是一种约定&#xff0c;它是浏览器最核心也最基本的安…...

fighting

目录Mysqlgroup by和 distinct哪个性能好java觉得Optional类怎么样isEmpty和isBlank的用法区别使用大对象时需要注意什么内存溢出和内存泄漏的区别及详解SpringResource和Autowired的起源既生“Resource”&#xff0c;何生“Autowired”使用Autowired时为什么Idea会曝出黄色警告…...

网络安全日志监控管理

内部安全的重要性 无论大小&#xff0c;每个拥有IT基础设施的组织都容易发生内部安全攻击。您的损失等同于黑客的收益&#xff1a;访问机密数据、滥用检索到的信息、系统崩溃&#xff0c;以及其他等等。专注于网络外部的入侵是明智的&#xff0c;但同时&#xff0c;内部安全也…...

线程池的使用:如何写出高效的多线程程序?

目录1.线程池的使用2.编写高效的多线程程序Java提供了Executor框架来支持线程池的实现&#xff0c;通过Executor框架&#xff0c;可以快速地创建和管理线程池&#xff0c;从而更加方便地编写多线程程序。 1.线程池的使用 在使用线程池时&#xff0c;需要注意以下几点&#xff…...

React 架构流程概览

React 架构流程概览 文章目录React 架构流程概览启动React项目流程分析各部分解析调度器协调器渲染器总结启动React项目 启动项目&#xff0c;并打开 Performance 面板 流程分析 首先找到入口函数 整个 render 下面的调用栈就是首屏渲染要执行的流程。 render 过程大致分为…...

【Linux】进程管理之kill、killall、pkill

一、kill 命令 Linux 中的 kill 命令用来终止指定的进程的运行&#xff0c;是 Linux 下进程管理的常用命令。通常&#xff0c;终止一个前台进程可以使用 CtrlC 键&#xff0c;但是&#xff0c;对于一个后台进程就须用 kill 命令来终止&#xff0c;那就需要先使用 ps、pidof、ps…...

LSTM从入门到精通(形象的图解,详细的代码和注释,完美的数学推导过程)

先附上这篇文章的一个思维导图什么是RNN按照八股文来说&#xff1a;RNN实际上就是一个带有记忆的时间序列的预测模型RNN的细胞结构图如下&#xff1a;softmax激活函数只是我举的一个例子&#xff0c;实际上得到y<t>也可以通过其他的激活函数得到其中a<t-1>代表t-1时…...

19.特殊工具与技术

文章目录特殊工具与技术19.1控制内存分配19.1.1重载new和deleteoperator new接口和operator delete接口malloc函数与free函数19.1.2定位new表达式显式的析构函数调用19.2运行时类型识别(run-time type identification, RTTI)19.2.1dynamic_cast运算符指针类型的dynamic_cast引用…...

518. 零钱兑换 II ——【Leetcode每日一题】

518. 零钱兑换 II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 3…...

电子网站有哪些/网络营销推广公司名称

前言Redis提供了5种数据类型&#xff1a;String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合)&#xff0c;理解每种数据类型的特点对于redis的开发和运维非常重要。Redis中的list是我们经常使用到的一种数据类型&#xff0c;根据使用方式的不同&#xff0c;可以…...

珠海网站建设公司有哪些/海南网站设计

参考链接&#xff1a;https://blog.csdn.net/csdn2314/article/details/90021367 authenticating with the app store 一直卡住最近更新App&#xff0c;要上传到 App Store 的时候&#xff0c;一直卡在 Authenticating with the App Store ,有人说 打开终端执行以下命令即可&am…...

wordpress获取子菜单/企业排名优化公司

Qt creator使用clang-format优化代码风格...

如何登录网站备案/友情链接怎么购买

转自&#xff1a;http://blog.csdn.net/stormbjm/article/details/9086163 1、添加用户&#xff0c;首先用adduser命令添加一个普通用户&#xff0c;命令如下&#xff1a; #adduser tommy //添加一个名为tommy的用户#passwd tommy //修改密码 Changing password for user tom…...

电影资源分享网站怎么做的/抖来查关键词搜索排名

权限管理权限表&#xff1a;1 订单管理2 用户管理3 菜单管理4 权限分配5 Bug管理用户表&#xff1a;1 Alex2 egon用户权限关系表&#xff1a;1 11 22 1import pymysqlclass DB:__conn pymysql.connect(host"localhost", user"root"…...

c 网站开发/免费技能培训在哪里报名

/*** * A:案例演示* 集合嵌套之ArrayList嵌套ArrayList* 案例:* 我们学科,学科又分为若个班级* 整个学科一个大集合* 若干个班级分为每一个小集合*/Testpublic void twoArrary() {ArrayList<ArrayList<Person>> list new ArrayList<>();ArrayList<Person…...