SpringAOP-Advisor
声明式配置
用法示例
@Pointcut("execution(* com.example.demo.Person.test(..))")
public void logPoint() {}
@Aspect
@Conponent // 如果看到有的写法这里没有 @Conponent 注解,一定是其他地方注入了这个 bean。要么就是使用了 AspectJ 的方式
@Order(1)
public class TestAdvice {
// 环绕通知:四合一通知
@Around("logPoint()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 1. 获取方法名
String methodName = pjp.getSignature().getName();
// 2. 获取类名
String className = pjp.getTarget().getClass().getSimpleName();
// 3. 获取参数列表(修改会影响目标方法)
Object[] args = pjp.getArgs();
// 4. 目标方法执行的时候,user 参数的id是123
User user = (User) args[0];
user.setId(123L);
try {
System.out.println("前置通知: Around");
Object result = pjp.proceed(); // 执行目标方法
System.out.println("返回通知: Around");
return result;
} catch (Exception e) {
System.out.println("异常通知: Around");
throw e;
} finally {
System.out.println("后置通知: Around");
}
}
// 前置通知:拦截注解
@Before("@annotation(MyAnnotation)")
public void beforeAdvice() {
System.out.println("前置通知: Before");
// return; 如果前置通知里 return,目标方法将不会执行
}
// 异常通知
@AfterThrowing(pointcut = "@annotation(MyAnnotation)", throwing = "ex") // 通过 throwing 指定异常参数名
public void afterThrowingAdvice(JoinPoint jp, Exception ex) {
System.out.println("异常通知: afterThrowing,发生异常:" + ex);
}
// 返回通知
@AfterReturning(pointcut = "@annotation(MyAnnotation)", returning = "result") // 通过 returning 指定返回值参数名
public void afterReturningAdvice(JoinPoint jp, Object result) {
System.out.println("返回通知:AfterReturning,结果为:" + result);
}
// 后置通知
@After("@annotation(MyAnnotation)")
public void afterAdvice() {
System.out.println("后置通知:logAfter");
}
}
通知参数
连接点参数
| 通知类型 | 参数 | 必须 | 说明 |
|---|---|---|---|
@Before |
JoinPoint |
否 | |
@AfterReturning |
JoinPoint |
否 | 通过 returning 属性绑定返回值 |
@AfterThrowing |
JoinPoint |
否 | 通过 throwing 属性绑定异常对象 |
@After |
JoinPoint |
否 | |
@Around |
ProceedingJoinPoint |
是 | 必须调用 proceed() 执行目标方法 |
自定义参数
示例1:单个参数
/**
* 表达式分两部分:execution(* com.example.service.*.*(..)) 和 args(id, ..)
* 第一部分 execution(* com.example.service.*.*(..)):就是确定切点的
* 第二部分 args(id, ..):表示把目标方法中的第一个参数映射到 id 上
*
* public void logUserId(Long id) 映射参数后就可以使用这个参数了,传入方法中
*/
@Before("execution(* com.example.service.*.*(..)) && args(id, ..)")
public void logUserId(Long id) {}
// 这个方法不会作为切点,因为类型不匹配
public String getUserName(Integer userId) {} // 方法参数是Integer
// 这个方法会作为切点
public String getUserName(Long userId) {}
示例2:多个参数
@Around("execution(* com.example.service.*.*(..)) && args(id, name, ..)")
public Object logParams(ProceedingJoinPoint pjp, Long id, String name){}
// 会被拦截
void method1(Long id, String name)
void method2(Long id, String name, Object... others)
// 不会被拦截
void method3(String name, Long id) // 参数顺序不对
void method4(Long id) // 缺少第二个String参数
编程式配置
用法示例
1. 前置通知 (MethodBeforeAdvice)
@Bean
public Advisor beforeAdvisor() {
// 1. 定义切点(匹配所有Service的public方法)
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(public * com.example.service.*.*(..))");
// 2. 定义前置通知
Advice advice = (MethodBeforeAdvice) (method, args, target) -> {
System.out.println("【前置通知】调用方法: " + method.getName());
System.out.println(" 参数: " + Arrays.toString(args));
};
advice.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置优先级最高
return new DefaultPointcutAdvisor(pointcut, advice);
}
2. 返回通知 (AfterReturningAdvice)
@Bean
public Advisor afterReturningAdvisor() {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("getUser*"); // 匹配getUser开头的方法
Advice advice = (AfterReturningAdvice) (returnValue, method, args, target) -> {
System.out.println("【返回通知】方法: " + method.getName());
System.out.println(" 返回值: " + returnValue);
};
return new DefaultPointcutAdvisor(pointcut, advice);
}
3. 异常通知 (ThrowsAdvice)
@Bean
public Advisor throwsAdvisor() {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern("com.example.service.*"); // 正则匹配
Advice advice = new ThrowsAdvice() {
// 拦截Exception类型异常
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("【异常通知】方法: " + method.getName());
System.out.println(" 异常: " + ex.getClass().getSimpleName());
}
};
return new DefaultPointcutAdvisor(pointcut, advice);
}
4. 环绕通知 (MethodInterceptor)
@Bean
public Advisor aroundAdvisor() {
// 切注解
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(Loggable.class);
Advice advice = (MethodInterceptor) invocation -> {
Method method = invocation.getMethod();
System.out.println("【环绕前置】" + method.getName());
try {
Object result = invocation.proceed();
System.out.println("【环绕返回】返回值: " + result);
return result;
} catch (Exception e) {
System.out.println("【环绕异常】" + e.getMessage());
throw e;
} finally {
System.out.println("【环绕后置】");
}
};
return new DefaultPointcutAdvisor(pointcut, advice);
}
5. 后置通知 (通过AfterReturningAdvice+ThrowsAdvice组合实现)
@Bean
public Advisor afterAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("@annotation(com.example.Audit)");
// 组合Advice实现最终效果
Advice advice = new org.springframework.aop.support.DelegatingIntroductionInterceptor() {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return super.invoke(mi);
} finally {
System.out.println("【后置通知】方法执行完成: " + mi.getMethod().getName());
}
}
};
return new DefaultPointcutAdvisor(pointcut, advice);
}
切点类型
-
AspectJExpressionPointcut:最强大的表达式匹配(声明式配置支持的表达式都可以) -
NameMatchMethodPointcut:简单方法名匹配NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.addMethodName("getUserById"); // 匹配 getUserById 方法 pointcut.addMethodName("save*"); // 匹配 save 开头的方法 -
AnnotationMatchingPointcut:注解匹配// 匹配类上有 @Service 注解的方法 AnnotationMatchingPointcut classPointcut = AnnotationMatchingPointcut.forClassAnnotation(Service.class); // 匹配方法上有 @Transactional 注解的方法 AnnotationMatchingPointcut methodPointcut = AnnotationMatchingPointcut.forMethodAnnotation(Transactional.class); -
JdkRegexpMethodPointcut:正则表达式匹配JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); pointcut.setPattern("com.example.service.*.query[A-Z].*"); // 匹配 query 打头的方法(方法名只能字符串) -
自定义切点
public class SuffixMatchPointcut extends StaticMethodMatcherPointcut { private final String suffix; public SuffixMatchPointcut(String suffix) { this.suffix = suffix; } @Override public boolean matches(Method method, Class<?> targetClass) { return method.getName().endsWith(suffix); } } // 使用示例:前置通知 @Bean public MethodBeforeAdvice beforeAdvisor() { return (method, args, target) -> { System.out.println("【后缀匹配拦截】方法 " + method.getName() + " 被调用,参数: " + Arrays.toString(args)); }; }
注意事项
- 切面类必须是 Spring bean,如果没有一定其他地方注入了这个 bean,要么就是 AspectJ 方式(看 jvm 参数,maven 依赖和插件可以判断)
- 只能拦截 public 普通方法,不能拦截构造方法,字段的访问等(如果需要这些功能要使用 AspectJ)
- 顺序问题,有可能被拦截多次,自己的拦截在最外层,内层的拦截报错了,导致自己的拦截信息不准确,可以把优先级调高(数字越小越优先)
- Spring 环境中需要使用注解
@EnableAspectJAutoProxy开启 AOP 功能,如果是 SpringBoot 环境不需要,内部已经自动开启了

浙公网安备 33010602011771号