【SpringBoot3】面向切面 AspectJ AOP 使用详解
2601_962203512026-08-31 13:19
文章目录* * [一、AspectJ介绍](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [二、简单使用步骤](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * * [1、引入依赖](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [2、定义一个Aspect](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [3、开启AOP支持](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [三、AOP 核心概念](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [四、切点(Pointcut)](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * * [1. execution](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [2. within](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [3. this & target](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [4. args & @args](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [5. @within & @target & @annotation](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [五、通知(Advice)](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * * [1. @Before 示例:日志记录](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [2. @AfterReturning 示例:处理返回值](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [3. @AfterThrowing 示例:异常处理](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [4. @After 示例:资源释放](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [5. @Around 示例:方法执行前后处理](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [6. 参数相关 Advice 示例:验证参数](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [7. 注解相关 Advice 示例:基于注解的日志记录](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [8. @AfterThrowing 异常示例:特定异常处理](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [9. @Pointcut 单独定义切点,其他Advice直接引用](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html) * [参考](### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点:1. 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void beforeAdvice() { System.out.println(“Before method: Do something before.”); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过@SpringBootApplication注解启动应用,该注解包含了@EnableAspectJAutoProxy,因此默认情况下Spring Boot应用是支持AOP的。如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy注解以开启对AspectJ自动代理的支持。### 三、AOP 核心概念 切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用@Aspect注解的常规类(@AspectJ风格)来实现。 * 连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。 * 通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。 * 切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。 * 引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现IsModified接口,以简化缓存。 * 目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。 * AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。 * 织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。 Spring AOP包括以下类型的通知:* 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。* 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。* 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。* 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。* 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。### 四、切点(Pointcut)Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:#### 1. executionexecution是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) * modifiers-pattern(可选):方法的修饰符,如public、private等。* ret-type-pattern:方法的返回类型,表示任意类型。 declaring-type-pattern(可选):声明方法的类,支持使用..来表示包及其子包下的所有类。* name-pattern:方法的名称。* param-pattern:方法的参数列表,..表示任意数量和类型的参数,表示任意类型的一个参数,(, String)表示第一个参数是任意类型,第二个参数是String类型。* throws-pattern(可选):方法抛出的异常类型。示例* 匹配任意类的任意方法:execution(* (..)) 匹配com.example.service包下所有类的所有方法:execution(* com.example.service...(..))* 匹配MyService类中的doSomething方法:execution(* com.example.service.MyService.doSomething(..))* 匹配任意类的save方法,且方法参数为java.lang.String类型:execution(* .save(java.lang.String))#### 2. withinwithin用于匹配指定类型内的方法执行,包括指定的接口、类或包。示例 匹配com.example.dao包下的所有类的所有方法:within(com.example.dao.) 匹配com.example.dao包及其子包中所有类中的所有方法:within(com.example.dao..) 匹配实现了UserService接口的类的所有方法:within(com.example.service.UserService+)#### 3. this & target* this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。* target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。示例* 匹配当前AOP代理对象类型为MyService的所有方法执行:this(com.example.service.MyService)* 匹配当前目标对象类型为MyService的所有方法执行:target(com.example.service.MyService)#### 4. args & @args* args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。* @args:匹配方法传入的参数所属类上拥有指定的注解的情况。示例* 匹配方法参数为String类型的方法:args(java.lang.String)* 匹配方法参数类型上拥有@MyAnnotation注解的方法调用:@args(com.example.MyAnnotation)#### 5. @within & @target & @annotation* @within:匹配类级别上应用了特定注解的类中的所有方法。* @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。* @annotation:匹配方法级别上应用了特定注解的方法。示例* 匹配所有类上具体指定了@MyAnnotation注解的类内的所有方法:@within(com.example.MyAnnotation)* 匹配当前目标对象类型持有@MyAnnotation注解的方法:@target(com.example.MyAnnotation)* 匹配所有拥有@MyAnnotation注解的外部调用方法:@annotation(com.example.MyAnnotation)### 五、通知(Advice)Spring AOP提供了多种类型的Advice,包括:1. @Before:在目标方法执行之前执行。2. @AfterReturning:在目标方法正常执行完成后执行。3. @AfterThrowing:在目标方法抛出异常时执行。4. @After:无论目标方法执行结果如何,最终都会执行。5. @Around:在目标方法执行前后执行,并可以控制目标方法的执行。每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。#### 1. @Before 示例:日志记录在方法执行之前记录日志,常用于跟踪方法的调用。 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…“表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before(“execution(* com.example.service.UserService.(…))") public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println(“Before executing method: " + joinPoint.getSignature().getName()); } // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”“表示类中的任意方法 // 第一次”…“表示包及其子包下的所有类 // 第二次”…"表示任意参数 @Before("execution( com.example…(…))") public void logBeforeMethodExecutionAll(JoinPoint joinPoint) { System.out.println("Before executing method all: " + joinPoint.getSignature().getName()); } } #### 2. @AfterReturning 示例:处理返回值在方法成功执行并返回后,对返回值进行处理或记录。 @Aspect @Component public class ReturnValueAspect { @AfterReturning(pointcut = "execution( com.example.service.UserService.getUserById(…))”, returning = “result”) public void logReturnValue(Object result) { System.out.println(“Method returned: " + result); } } #### 3. @AfterThrowing 示例:异常处理在方法抛出异常时捕获异常,并进行相应的处理或记录。 @Aspect @Component public class ExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleException(Exception ex) { System.err.println(“An exception occurred: " + ex.getMessage()); } } #### 4. @After 示例:资源释放无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。 @Aspect @Component public class ResourceReleaseAspect { @After(“execution(* com.example.service..(…))”) public void releaseResources() { System.out.println(“Resources are released after method execution”); } } #### 5. @Around 示例:方法执行前后处理在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。 @Aspect @Component public class AroundAdviceAspect { @Around(“execution(* com.example.service.UserService.(…))") public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("After executing method: " + joinPoint.getSignature().getName()); return result; } } #### 6. 参数相关 Advice 示例:验证参数在方法执行之前验证参数的有效性。 @Aspect @Component public class ParameterValidationAspect { @Before("execution( com.example.service.UserService.addUser(com.example.model.User)) && args(user)”) public void validateUserParameter(User user) { if (user == null || user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException(“User parameter is invalid”); } } } #### 7. 注解相关 Advice 示例:基于注解的日志记录根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } @Aspect @Component public class AnnotationDrivenLoggingAspect { @Before(”@annotation(com.example.aspect.Loggable)”) public void logMethodWithAnnotation(JoinPoint joinPoint) { System.out.println(“Executing loggable method: " + joinPoint.getSignature().getName()); } // 使用参数中的注解 @Before(”@annotation(loggable)”) public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) { System.out.println("Executing loggable method: " + joinPoint.getSignature().getName()); } } // 在需要日志记录的方法上使用@Loggable注解 @Service public class MyService { @Loggable public void myLoggableMethod() { // … } } #### 8. @AfterThrowing 异常示例:特定异常处理仅当方法抛出特定类型的异常时进行处理。 @Aspect @Component public class SpecificExceptionHandlingAspect { @AfterThrowing(pointcut = “execution(* com.example.service..(…))”, throwing = “ex”) public void handleSpecificException(CustomException ex) { System.err.println(“A CustomException occurred: " + ex.getMessage()); } } // 自定义异常类 public class CustomException extends RuntimeException { public CustomException(String message) { super(message); } } #### 9. @Pointcut 单独定义切点,其他Advice直接引用 @Aspect @Component public class LoggingAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个”“表示任意返回类型,第二个”"表示类中的任意方法 // "…"表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Pointcut("execution(* com.example.service.UserService.(…))") private void service() { } @Before(“service()”) public void logBeforeMethodExecution(JoinPoint joinPoint) { System.out.println("Before executing method: " + joinPoint.getSignature().getName()); } } ### 参考 https://docs.spring.io/spring-framework/reference/core/aop.html)### 一、AspectJ介绍AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。* 特点 :1. 模块化 :AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。2. 声明式编程 :AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。3. 细粒度控制 :AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。4. 跨模块切面 :AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。5. 与Java语言的兼容性 :AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。### 二、简单使用步骤#### 1、引入依赖 org.springframework.boot spring-boot-starter-aop #### 2、定义一个Aspect import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个""表示任意返回类型,第二个" "表示类中的任意方法 // "..."表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before("execution(* com.example.service.UserService.(...))") public void beforeAdvice() { System.out.println("Before method: Do something before."); } } #### 3、开启AOP支持在Spring Boot应用中,通常通过
相关推荐
程序员大雄学编程16 分钟前
微积分43. 无穷积分入门:从概念到Python实战可视化何以解忧,唯有..16 分钟前
LangChain 中 Pydantic 格式输出:结构化输出的完整指南测试老哥22 分钟前
Postman获取token并设置token依赖步骤郝学胜-神的一滴25 分钟前
C++11 工程级应用 06:自己造一个类似Python的Range迭代器满怀冰雪27 分钟前
23-PaddleClas 数据集、配置文件与训练参数详解zh_xuan36 分钟前
c++ jthread旧梦952737 分钟前
Java 捕获多个异常:从基础语法到最佳实践XLYcmy42 分钟前
HTML/CSS/JS 基础与 Vue 技术栈深度解析新知图书1 小时前
12.3 智能体中Skill的约束与自进化实战