1. Advisor 的基本概念与核心价值
在Spring框架中,Advisor是一个非常重要的概念,它代表了AOP(面向切面编程)中的"通知器"。简单来说,Advisor就是把"在什么地方"(Pointcut)和"做什么"(Advice)这两件事绑定在一起的组合对象。想象一下,如果你要在代码中插入一些横切关注点(比如日志、事务等),Advisor就是帮你精确控制这些插入行为的"指挥官"。
Spring内置了多种Advisor实现,它们各自有不同的特点和适用场景。这些内置Advisor就像是Spring为你准备好的"瑞士军刀",开箱即用,能解决大部分常见的AOP需求。比如DefaultPointcutAdvisor、NameMatchMethodPointcutAdvisor等,都是我们在日常开发中经常会用到的。
提示:理解Advisor的关键是要明白它连接了Pointcut和Advice这两个AOP的核心概念。Pointcut决定"在哪里切入",Advice决定"切入后做什么",而Advisor就是把它们组合起来的桥梁。
2. Spring内置Advisor的实现原理
2.1 DefaultPointcutAdvisor详解
DefaultPointcutAdvisor是Spring中最基础、最灵活的Advisor实现。它的灵活性体现在你可以完全自定义Pointcut和Advice的组合。下面是一个典型的使用示例:
java复制// 创建一个方法匹配的Pointcut
Pointcut pointcut = new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().startsWith("save");
}
};
// 创建一个前置通知
Advice advice = new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("准备执行save操作...");
}
};
// 组合成Advisor
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
在实际项目中,我经常用DefaultPointcutAdvisor来实现一些特定的横切逻辑。它的优势在于灵活性高,但缺点是需要手动编写Pointcut的判断逻辑,相对麻烦一些。
2.2 NameMatchMethodPointcutAdvisor解析
NameMatchMethodPointcutAdvisor是Spring提供的另一个常用Advisor实现,它简化了基于方法名称的匹配。当你只需要根据方法名来匹配切入点时,这个Advisor特别方便:
java复制NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("准备执行" + method.getName() + "操作...");
}
});
advisor.setMappedNames("save*", "update*", "delete*");
从实现原理上看,NameMatchMethodPointcutAdvisor内部使用了NameMatchMethodPointcut,它通过简单的字符串匹配(支持通配符)来确定切入点。这种方式的优点是配置简单直观,缺点是匹配规则相对简单,无法实现复杂的匹配逻辑。
3. Advisor在Spring AOP中的工作流程
3.1 Advisor的注册与发现机制
在Spring中,Advisor通常是通过配置方式注册到容器中的。无论是XML配置还是Java配置,最终都会被解析成Advisor对象。Spring AOP的核心组件(主要是AopProxy)在创建代理对象时,会从容器中获取所有适用的Advisor。
一个常见的误区是认为所有Advisor都会应用到所有Bean上。实际上,Spring会通过ClassFilter和MethodMatcher两层过滤机制,确保Advisor只应用到匹配的Bean和方法上。这也是为什么我们在定义Pointcut时,通常需要同时考虑类级别和方法级别的匹配。
3.2 Advisor的执行顺序控制
当多个Advisor应用到同一个方法上时,它们的执行顺序就变得非常重要。Spring提供了两种控制顺序的方式:
- 实现Ordered接口:Advisor可以实现Ordered接口,通过getOrder()方法返回一个整数值,数值越小优先级越高。
- 使用@Order注解:在基于注解的配置中,可以在Advisor类上添加@Order注解。
在实际项目中,我曾经遇到过因为Advisor执行顺序不当导致的bug:一个事务Advisor和一个缓存Advisor同时作用在同一个方法上,由于顺序不对,导致缓存先于事务执行,结果缓存了错误的数据。后来通过正确设置Order值解决了这个问题。
4. 高级Advisor实现与应用场景
4.1 自定义Advisor的实现
虽然Spring提供了多种内置Advisor,但有时候我们需要实现一些特殊逻辑的自定义Advisor。下面是一个自定义Advisor的实现示例:
java复制public class CustomAnnotationAdvisor extends AbstractPointcutAdvisor {
private final Pointcut pointcut;
private final Advice advice;
public CustomAnnotationAdvisor() {
this.pointcut = new AnnotationMatchingPointcut(CustomAnnotation.class, true);
this.advice = new CustomAnnotationAdvice();
}
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
}
这个自定义Advisor会匹配所有带有@CustomAnnotation注解的方法。在实际项目中,这种基于自定义注解的Advisor非常有用,可以实现各种业务特定的横切逻辑。
4.2 Advisor在Spring AI中的应用
从网络热词中可以看到,Spring AI是一个新兴的热点。在Spring AI中,Advisor模式同样可以发挥重要作用。比如,我们可以实现一个Advisor来自动为AI服务调用添加日志、监控或重试逻辑:
java复制public class AIServiceAdvisor extends AbstractPointcutAdvisor {
private final Pointcut pointcut;
private final Advice advice;
public AIServiceAdvisor() {
this.pointcut = new ComposablePointcut(
new AnnotationMatchingPointcut(AIService.class, true)
);
this.advice = new RetryAdvice();
}
// 省略getter方法
}
这个Advisor会自动为所有标注了@AIService的类添加重试逻辑,这在调用不稳定的AI服务时特别有用。
5. Advisor性能优化与最佳实践
5.1 Advisor的性能考量
虽然Advisor非常强大,但不合理的使用会影响应用性能。以下是一些性能优化的建议:
- Pointcut匹配尽量精确:过于宽泛的Pointcut会导致大量方法被代理,增加运行时开销。
- 避免在Pointcut匹配中做复杂计算:特别是避免在matches()方法中执行IO或网络操作。
- 合理使用静态Pointcut:如果可能,优先使用StaticMethodMatcherPointcut而不是DynamicMethodMatcherPointcut,因为静态匹配在代理创建时就能确定,而动态匹配每次方法调用都需要检查。
5.2 常见问题排查
在实际使用Advisor时,经常会遇到一些问题。以下是一些常见问题及其解决方案:
-
Advisor不生效:
- 检查Bean是否被正确代理(可能是由于Spring AOP只能代理public方法)
- 检查Pointcut是否确实匹配目标方法
- 确保Advisor已经被正确注册到Spring容器
-
循环依赖问题:
- 当Advisor和被代理的Bean相互依赖时可能导致循环依赖
- 解决方案是尽量避免Advisor依赖被代理的Bean
-
性能问题:
- 使用AopProxyUtils.ultimateTargetClass()获取原始类,避免多层代理带来的性能损耗
- 考虑使用AspectJ编译时织入替代Spring AOP运行时代理
在最近的一个项目中,我们遇到了Advisor导致启动变慢的问题。经过分析发现是因为配置了太多细粒度的Advisor,每个Advisor都需要扫描所有Bean。后来我们合并了一些功能相似的Advisor,并优化了Pointcut的匹配逻辑,显著提高了启动速度。
