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 {
    @Override
    public int add(int a, int b) {
        int i= a+b;
        System.out.println("被代理目标执行的add");
        return i;
    }

    @Override
    public int sub(int a, int b) {
        int i= a-b;
        System.out.println("被代理目标执行的sub");
        return i;
    }

    @Override
    public int mul(int a, int b) {
        int i= a*b;
        System.out.println("被代理目标执行的mul");
        return i;
    }

    @Override
    public 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 {
    @Test
    public 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关键字,不要问为什么这样写。很简单,因为是文档要求,先看一下运行结果。

    @Test
    public 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("调用目标的方法之前调用");
    }

}

然后调用:

  @Test
    public 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之后,其实是无法得到目标对象的,比如:

    @Test
    public 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("调用目标的方法之后调用");
    }

然后调用:

  @Test
    public 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的后置通知,是无论是异常退出还是正常推出都会被执行。

比如调用除法:

    @Test
    public 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);
    }

如果正常调用:

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

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

在这里插入图片描述

但是估计来一个错误的:

 @Test
    public 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("环绕通知---------------后置通知");
    }

    }
}

然后通过调用:

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

看一下结果:

在这里插入图片描述

然后再试一下异常通知:

  @Test
    public 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.xsd
     http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://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.xsd
     http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://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了解即可,一般的时候更侧重于用注解进行配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值