AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。
AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。
org.springframework.boot
spring-boot-starter-aop
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.");
}
}
在Spring Boot应用中,通常通过@SpringBootApplication
注解启动应用,该注解包含了@EnableAspectJAutoProxy
,因此默认情况下Spring Boot应用是支持AOP的。
如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy
注解以开启对AspectJ自动代理的支持。
切面(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包括以下类型的通知:
环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。
所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。
Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:
execution
是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
public
、private
等。*
表示任意类型。..
来表示包及其子包下的所有类。..
表示任意数量和类型的参数,*
表示任意类型的一个参数,(*, String)
表示第一个参数是任意类型,第二个参数是String类型。示例
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))
within
用于匹配指定类型内的方法执行,包括指定的接口、类或包。
示例
com.example.dao
包下的所有类的所有方法:within(com.example.dao.*)
com.example.dao
包及其子包中所有类中的所有方法:within(com.example.dao..*)
UserService
接口的类的所有方法:within(com.example.service.UserService+)
示例
MyService
的所有方法执行:this(com.example.service.MyService)
MyService
的所有方法执行:target(com.example.service.MyService)
示例
String
类型的方法:args(java.lang.String)
@MyAnnotation
注解的方法调用:@args(com.example.MyAnnotation)
示例
@MyAnnotation
注解的类内的所有方法:@within(com.example.MyAnnotation)
@MyAnnotation
注解的方法:@target(com.example.MyAnnotation)
@MyAnnotation
注解的外部调用方法:@annotation(com.example.MyAnnotation)
Spring AOP提供了多种类型的Advice,包括:
每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。
在方法执行之前记录日志,常用于跟踪方法的调用。
@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());
}
}
在方法成功执行并返回后,对返回值进行处理或记录。
@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);
}
}
在方法抛出异常时捕获异常,并进行相应的处理或记录。
@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());
}
}
无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。
@Aspect
@Component
public class ResourceReleaseAspect {
@After("execution(* com.example.service.*.*(..))")
public void releaseResources() {
System.out.println("Resources are released after method execution");
}
}
在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。
@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;
}
}
在方法执行之前验证参数的有效性。
@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");
}
}
}
根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。
@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() {
// ...
}
}
仅当方法抛出特定类型的异常时进行处理。
@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);
}
}
@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());
}
}