aop通知

Last updated: ... / Reads: 33 Edit

在 Spring AOP 中,通知(advice)是切面的一部分,它定义了在何时、何地以及如何应用切面逻辑。通知可以在目标方法执行前、执行后或者在执行过程中(环绕通知)被执行。以下是几种常见的 AOP 通知类型:

  1. 前置通知(Before advice):

    • 在目标方法执行之前执行。
    • 可以用于执行一些前置逻辑,如权限检查、日志记录等。
    • 使用 @Before 注解。
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        // 前置逻辑
    }
    
  2. 后置通知(After returning advice):

    • 在目标方法成功执行后执行。
    • 可以用于处理方法返回值、日志记录等。
    • 使用 @AfterReturning 注解。
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturningAdvice(Object result) {
        // 后置逻辑,可以访问目标方法的返回值
    }
    
  3. 异常通知(After throwing advice):

    • 在目标方法抛出异常时执行。
    • 用于处理异常、记录日志等。
    • 使用 @AfterThrowing 注解。
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
    public void afterThrowingAdvice(Exception exception) {
        // 异常处理逻辑
    }
    
  4. 后置通知(After advice):

    • 无论目标方法成功执行与否,在方法执行后都会执行。
    • 常用于资源清理等。
    • 使用 @After 注解。
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice() {
        // 后置逻辑
    }
    
  5. 环绕通知(Around advice):

    • 在目标方法执行前和执行后都会执行。
    • 提供最大的灵活性,可以完全控制目标方法的执行。
    • 使用 @Around 注解,需要使用 ProceedingJoinPoint 来手动控制目标方法的执行。
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 环绕通知前逻辑
    
        Object result = proceedingJoinPoint.proceed();  // 执行目标方法
    
        // 环绕通知后逻辑
    
        return result;
    }
    

通过使用这些不同类型的通知,可以实现对目标方法在不同阶段的控制和处理。在实际应用中,可以根据需求选择合适的通知类型来实现特定的切面逻辑。

切入点表达式

通知语法中的切入点表达式(Pointcut Expression)使用 AspectJ 的语法,它允许你定义在哪些地方(哪些方法)应用通知。以下是一些常见的通知语法细节:

  1. 通配符 *

    • * 表示匹配任意字符,通常用于匹配方法名、类名或包名中的部分内容。
    • 示例:
      • execution(* com.example.service.*.*(..)): 匹配 com.example.service 包下的所有类的所有方法。
  2. 两个点 ..

    • .. 表示匹配任意数量的参数。
    • 示例:
      • execution(* com.example.service.*.*(..)): 匹配 com.example.service 包下的所有类的所有方法,无论方法参数的数量是多少。
  3. 全限定类名:

    • 可以使用全限定类名来匹配指定的类。
    • 示例:
      • execution(* com.example.service.MyService.*(..)): 匹配 com.example.service 包下的 MyService 类的所有方法。
  4. 方法名匹配:

    • 可以使用方法名来匹配特定的方法。
    • 示例:
      • execution(* com.example.service.MyService.doSomething(..)): 匹配 MyService 类中的 doSomething 方法。
  5. 参数类型匹配:

    • 可以使用参数类型来匹配特定的方法。
    • 示例:
      • execution(* com.example.service.MyService.someMethod(String, int)): 匹配 MyService 类中的 someMethod 方法,该方法有一个 String 类型和一个 int 类型的参数。
  6. 组合使用:

    • 可以通过逻辑运算符 &&(与)、||(或)、!(非)来组合多个条件。
    • 示例:
      • execution(* com.example.service.*.*(String) && args(myArg)): 匹配包名为 com.example.service 下的所有类的方法,方法参数为一个 String 类型且值为 myArg
  7. 注解匹配:

    • 使用 @annotation 可以匹配被特定注解标注的方法。
    • 示例:
      • @Before("@annotation(com.example.annotation.Loggable))": 在所有被 @Loggable 注解标注的方法前执行前置通知。

这些是一些常见的通知语法细节,根据实际需要,可以灵活组合使用这些元素来定义切入点表达式,从而实现对目标方法的精确匹配。

语法细节

  • *号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限。
  • 在包名的部分,一个*号只能代表包的层次结构中的一层,表示这一层是任意的。例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
  • 在包名的部分,使用*..表示包名任意、包的层次深度任意。
  • 在类名的部分,类名部分整体用*号代替,表示类名任意。
  • 在类名的部分,可以使用*号代替类名的一部分。例如:*Service匹配所有名称以Service结尾的类或接口。
  • 在方法名部分,可以使用*号表示方法名任意。
  • 在方法名部分,可以使用*号代替方法名的一部分。例如:*Operation匹配所有方法名以Operation结尾的方法。
  • 在方法参数列表部分,使用(..)表示参数列表任意。
  • 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头。
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的。
  • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的。
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符。例如:
    • execution(public int ..Service.(.., int)) 正确。
    • execution( int ..Service.*(.., int)) 错误。

微信截图_20240203124638

重用切入点表达式

在Spring AOP中,可以通过给切入点表达式命名并重用它们来提高代码的可维护性。这可以通过@Pointcut注解来实现,将切入点表达式定义在一个方法中,然后在通知方法中引用这个方法。这样可以减少代码冗余,使代码更清晰和易读。以下是一个简单的示例:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect {

    // 定义切入点表达式
    @Pointcut("execution(* com.example.service.*.*(..))")
    private void serviceMethods() {}

    // 应用切入点表达式的前置通知
    @Before("serviceMethods()")
    public void beforeServiceMethods() {
        System.out.println("Before executing service methods");
    }
}

在不同切面中重用切入点表达式同样是可能的。通过将切入点表达式定义为一个独立的方法,并在不同的切面中引用它,可以实现切入点表达式的重用。以下是一个示例:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MyAspect1 {

    // 定义切入点表达式
    @Pointcut("execution(* com.example.service.*.*(..))")
    private void serviceMethods() {}

    // 应用切入点表达式的前置通知
    @Before("serviceMethods()")
    public void beforeServiceMethods() {
        System.out.println("Before executing service methods in MyAspect1");
    }
}

@Aspect
public class MyAspect2 {

    // 引用另一个切面的切入点表达式
    @Pointcut("com.example.aspect.MyAspect1.serviceMethods()")
    private void serviceMethodsInMyAspect1() {}

    // 应用切入点表达式的前置通知
    @Before("serviceMethodsInMyAspect1()")
    public void beforeServiceMethodsInMyAspect1() {
        System.out.println("Before executing service methods in MyAspect2");
    }
}

在上面的例子中,MyAspect2 切面引用了 MyAspect1 切面中定义的切入点表达式 serviceMethods()。这样,两个切面都可以重用相同的切入点表达式,实现了代码的重用和可维护性。

需要注意的是,在不同的切面中引用切入点表达式时,需要使用全限定类名来指定切面的位置。在@Pointcut注解中,使用com.example.aspect.MyAspect1.serviceMethods()来引用MyAspect1中的切入点表达式。 在上面的例子中,通过@Pointcut注解定义了一个切入点表达式的方法 serviceMethods(),该方法包含了匹配 com.example.service 包下的所有类的所有方法的表达式。然后,在前置通知方法 beforeServiceMethods() 中使用 @Before 注解引用了这个切入点表达式。

通过这种方式,如果切入点表达式需要修改,只需在一个地方进行修改,不需要在多个通知方法中重复定义相同的表达式。这提高了代码的可维护性,同时使切入点的定义更加集中和清晰。


Comments

Make a comment