Spring AOP 应用篇

3,177 阅读9分钟

添加依赖

使用Spring AOP必须添加AOP的依赖包,并配置AOP

  • Spring MVC中添加并配置 AOP
    • 在Maven中添加AOP依赖
    <!--
        只需要导入 spring-webmvc 这一个包,maven就会自动下载以下依赖包
        spring-core —— Spring的核心组件
        spring-beans —— SpringIoC(依赖注入)的基础实现
        spring-aop ——Spring的面向切面编程,提供AOP(面向切面编程)实现
        spring-context —— Spring提供在基础IoC功能上的扩展服务
        spring-expression —— Spring表达式语言
        spring-web —— SpringMVC支持WEB端应用部署架构
        spring-webmvc —— REST Web服务和Web应用的视图控制器的实现
    -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    
    <!-- aop aspect 相关jar包-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
    
    • 在spring-MVC配置文件中,添加配置
    <!-- 激活Spring组件扫描功能,自动扫描指定包及其子包下面通过注解配置的组件 -->
    <context:component-scan base-package="com.test.aop"/>
    
    <!-- 启动AspectJ支持proxy-target-class="true"指定spring使用cglib来生成代理方法。
    不填Spring则根据条件从cglib和java动态代理中选择 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    
  • Spring Boot添加并配置AOP
<!-- 只需要导入这个包,Maven就会下载以下依赖包 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

由于用的是SpringBoot,所以也不需要配置aop

切面表达式

概览

匹配包/类型

within()

// 匹配service类里头的所有方法
@Pointcut("within(com.test.service)")
public void matchType(){}

// 匹配com.test包及子包下所有类的方法
@Pointcut("within(com.test..*)"
public void matchPackage(){}

匹配注解

@annotation()

/**
 * @annotation 匹配方法级别
 * 如下,匹配 标注有 @ToExcel注解 的方法
 */
@Pointcut("@annotation(com.demo.security.ToExcel)")
public void annotation(){}

@within()

/**
 * @within 匹配类级别
 * 非 Spring环境下,要求的 annotation 的 RetentionPolicy 级别为 CLASS
 * 如下,匹配 标注有 @Service注解 的类 的所有方法
 */
@Pointcut("@within(org.springframework.stereotype.Service)")
public void within(){}

@target()

/**
 * @target 匹配类级别
 * 非 Spring环境下,要求的 annotation 的 RetentionPolicy 级别为 RUNTIME
 * 如下,匹配 标注有 @Service注解 的类 的所有方法
 */
@Pointcut("@target(org.springframework.stereotype.Service)")
public void target(){}

@args()

/**
 * @args 匹配参数级别
 * 如下,匹配 某方法的参数 所属的类 标注有 authority注解 的方法
 * 即,被拦截的方法的参数中,有的参数所属的类 标注有 authority注解
 */
@Pointcut("@args(com.test.authority)")
public void args(){}

匹配对象

this()

// ps:这个还没弄清楚,就不误人子弟了
@Pointcut("this(com.test.DemoDao)")
public void thisDemo() {}

target()

// ps:这个还没弄清楚,就不误人子弟了
@Pointcut("target(com.test.IDao)")
public void targetDemo() {}

bean()

// 匹配 Spring bean 容器中,所有名称以 Service 结尾的 bean
@Pointcut("bean(*Service)")
public void beanDemo() {}

匹配参数

execution()

//匹配任何名称以 find 开头而且只有一个 Long 参数的方法
@Pointcut("execution(* *..find*(Long))")
public void execution1() {
}

//匹配任何名称以 find 开头的而且第一个参数为 Long 类型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void execution2() {
}

args()

//匹配任何 只有一个Long参数 的方法
@Pointcut("args(Long)")
public void args1() {
}

//匹配第一个参数为 Long 类型的方法
@Pointcut("args(Long,..)")
public void args2() {
}

execution()表达式

结构

execution(<修饰符>? <返回值类型> <方法>(<参数列表>) <异常>?)

  • 带?的,是可选项目,其他的为必选项

实例

/**
 * execution(<修饰符>? <返回值类型> <方法>(<参数列表>) <异常>?)
 * 如下,
 * 匹配 修饰符为 public,
 * 返回值类型为任意类型,
 * 方法为 com.test.service包中 以Service结尾的类的所以方法,
 * 参数列表为任意参数,
 * 异常为java.lang.IllegalAccessException
 * 注意,如果指定了异常,那么只会匹配 throws 了 指定异常的方法!!!
 */
@Pointcut("execution(public * com.test.service.*Service.*(..) throws java.lang.IllegalAccessException)")
public void execution() {
}

切面的注解

@Pointcut()

作用: 定义一个切入点

@Pointcut()注解的value参数: 一个切面表达

实例

/**
 * @Pointcut 注解,用于定义一个织入点
 *
 * 如下,
 * 匹配 修饰符为 public,
 * 返回值类型为任意类型,
 * 方法为 com.test.service包中 以Service结尾的类的所以方法,
 * 参数列表为任意参数,
 * 异常为java.lang.IllegalAccessException
 *
 * 的方法为织入点
 */
@Pointcut("execution(public * com.test.service.*Service.*(..) throws java.lang.IllegalAccessException)")
public void log() {}

@Before()

作用: 被打上 @Before 注解的方法,会在目标方法执行之前执行

@Before()注解的value参数: 除了是一个切面表达式之外,还可以是一个定义好的织入点

实例

/**
 * @Before 注解的参数 可以是一个切面表达式,也可以是一个织入点
 * 如下,是一个名为log()的织入点
 * 此@Before注解 将匹配log()织入点匹配到的方法
 */
@Before("log()")
public void before(){
    System.out.println("此语句输出在目标方法执行之前");
}

@After()

作用: 被打上 @After 注解的方法,会在目标方法执行之后执行,不管目标方法是否成功执行或抛出异常

@After()注解的value参数: 除了是一个切面表达式之外,还可以是一个定义好的织入点

实例

/**
 * @After 注解的参数 可以是一个切面表达式,也可以是一个织入点
 * 如下,此@After 将匹配log()织入点 或 切面表达式匹配到的方法
 */
@After("log() || @annotation(com.demo.security.ToExcel)")
public void After(){
    System.out.println("此语句输出在目标方法执行之后");
}

@Around()

作用: 被打上 @After 注解的方法,会将目标方法“包围”起来, 在目标方法执行前后做一些操作

@Around()注解的value参数: 除了是一个切面表达式之外,还可以是一个定义好的织入点

实例

/**
 * 打上 @After 注解的方法,会将目标方法“包围”起来,在目标方法执行前后做一些操作
 * 如下,将匹配log()织入点中方法参数名为 token 的方法
 * 并将此token参数 和 ProceedingJoinPoint对象 作为入参
 */
@Around(value = "log() && args(token)")
public Object Around(ProceedingJoinPoint joinPoint, String token) throws Throwable {
    System.out.println("拦截方法的token参数值为:" + token);

    System.out.println("此语句输出在目标方法执行之前");
    try {
        // 执行目标方法,并返回目标方法的执行结果
        Object result = joinPoint.proceed(joinPoint.getArgs());
        return result;
    } catch (Throwable throwable) {
        System.out.println("出现异常");
        // 如果目标方法出现异常,不要`生吞`异常,最好原样抛出
        throw throwable;
    } finally {
        // @After注解 相当于 finally的语句,不管目标方法是否成功执行或抛出异常,都会执行
        System.out.println("此语句输出在目标方法执行之后");
    }
}

@AfterReturning()

作用: 此注解与@After注解作用一样,不同之出在于它多了一个 returning参数,可以用来获取目标方法返回值,并作为入参带入方法中

@AfterReturning()注解的value参数: 除了是一个切面表达式之外,还可以是一个定义好的织入点

实例

/**
 * 使用 returning 获取目标方法返回值,并取名为result,再将result作为参数带入方法中
 */
@AfterReturning(value = "log() || @annotation(com.demo.security.ToExcel)", returning = "result")
public void AfterReturning(Object result) {
    // 打印目标方法返回结果
    System.out.println(result);
    System.out.println("此语句输出在目标方法执行之后");
}

@AfterThrowing()

作用: 此注解不同于@After注解,它只有在目标方法抛出异常之后才会执行

@AfterThrowing()注解的value参数: 除了是一个切面表达式之外,还可以是一个定义好的织入点

实例

/**
 *使用 throwing 获取目标方法抛出的异常,并取名为e,再将 e 作为参数带入方法中
 */
@AfterThrowing(value="log()", throwing="e")
public void AfterThrowing(Exception e){
    //处理异常
    e.getMessage();
    System.out.println("此方法在 目标方法抛出异常时 才执行");
}

自定义切面类

首先创建一个java类,然后打上 @Aspect 和 @Component 注解,一个切面就定义好了。

实例

package com.example.demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
* 自定义切面需要在类上面打上两个注解
*
* @Aspect注解 用于标识这个类是一个自定义切面
* @Component注解 用于将此类交给 Spring 管理
*/
@Aspect
@Component
public class Test {

   /**
    * @Pointcut 注解,用于定义一个织入点
    * <p>
    * 如下,
    * 匹配 修饰符为 public,
    * 返回值类型为任意类型,
    * 方法为 com.test.service包中 以Service结尾的类的所以方法,
    * 参数列表为任意参数,
    * 异常为java.lang.IllegalAccessException
    * <p>
    * 的方法为织入点
    */
   @Pointcut("execution(public * com.test.service.*Service.*(..) throws java.lang.IllegalAccessException)")
   public void log() {
   }


   /**
    * 打上 @Before 注解的方法,会在目标方法执行之前执行
    *
    * @Before 注解的参数 可以是一个切面表达式,也可以是一个织入点
    * 如下,是一个名为log()的织入点
    * 此@Before注解 将匹配log()织入点匹配到的方法
    */
   @Before("log()")
   public void before() {
       System.out.println("此语句输出在目标方法执行之前");
   }


   /**
    * 被打上 @After 注解的方法,会在目标方法执行之后执行,不管目标方法是否成功执行或抛出异常
    *
    * @After 注解的参数 可以是一个切面表达式,也可以是一个织入点
    * 如下,此@After 将匹配log()织入点 或 切面表达式匹配到的方法
    */
   @After("log() || @annotation(com.demo.security.ToExcel)")
   public void After() {
       System.out.println("此语句输出在目标方法执行之后");
   }


   /**
    * 打上 @After 注解的方法,会将目标方法“包围”起来,在目标方法执行前后做一些操作
    * 如下,将匹配log()织入点中方法参数名为 token 的方法
    * 并将此token参数 和 ProceedingJoinPoint对象 作为入参
    */
   @Around(value = "log() && args(token)")
   public Object Around(ProceedingJoinPoint joinPoint, String token) throws Throwable {
       System.out.println("拦截方法的token参数值为:" + token);

       System.out.println("@After此语句输出在目标方法执行之前");
       try {
           // 执行目标方法,并返回目标方法的执行结果
           Object result = joinPoint.proceed(joinPoint.getArgs());
           return result;
       } catch (Throwable throwable) {
           System.out.println("出现异常");
           // 如果目标方法出现异常,不要`生吞`异常,最好原样抛出
           throw throwable;
       } finally {
           // @After注解 相当于 finally的语句,不管目标方法是否成功执行或抛出异常,都会执行
           System.out.println("此语句输出在目标方法执行之后");
       }
   }


   /**
    * 使用 returning 获取目标方法返回值,并取名为result,再将result作为参数带入方法中
    */
   @AfterReturning(value = "log() || @annotation(com.demo.security.ToExcel)", returning = "result")
   public void AfterReturning(Object result) {
       // 打印目标方法返回结果
       System.out.println(result);
       System.out.println("此语句输出在目标方法执行之后");
   }

   /**
    * 使用 throwing 获取目标方法抛出的异常,并取名为e,再将 e 作为参数带入方法中
    */
   @AfterThrowing(value = "log()", throwing = "e")
   public void AfterThrowing(Exception e) {
       //处理异常
       e.getMessage();
       System.out.println("此方法在 目标方法抛出异常时 才执行");
   }
}