引言
Spring AOP(面向切面编程)是Spring框架的"灵魂"特性之一,通过动态代理技术实现业务逻辑的解耦。本文将结合SpringSelectProxyCase
(代理创建与执行)和CutPointMatchingCase
(切点匹配)两段核心代码,从代理对象的底层生成逻辑 、通知的完整执行链路 、切点匹配的底层算法三个维度,深入解析Spring AOP的运行时原理,并结合反射、字节码增强、注解处理等Java核心技术,揭示AOP的"黑箱"细节。
一、代理对象的底层创建:从ProxyFactory到动态代理类
1.1 ProxyFactory的核心组件与初始化流程
ProxyFactory
是Spring AOP代理创建的"总导演",其内部通过AdvisedSupport
对象管理代理的核心配置(目标对象、切面、代理类型等)。我们通过SpringSelectProxyCase
中的代码片段展开分析:
java
// 2.1 初始化代理工厂(ProxyFactory核心作用:管理目标对象、切面,生成代理)
ProxyFactory proxyFactoryA = new ProxyFactory();
proxyFactoryA.setTarget(targetA); // 设置目标对象
proxyFactoryA.setInterfaces(targetA.getClass().getInterfaces()); // 设置目标对象实现的接口(JDK代理必须)
proxyFactoryA.addAdvisor(advisor); // 添加切面
1.1.1 ProxyFactory的内部结构
ProxyFactory
继承自AdvisedSupport
,而AdvisedSupport
包含以下核心属性(通过debug
源码可知):
属性名 | 类型 | 作用描述 |
---|---|---|
targetSource |
TargetSource |
目标对象的包装类,支持延迟加载、原型模式等高级特性(默认SingletonTargetSource ) |
advisors |
List<Advisor> |
存储所有切面(Advisor),每个Advisor包含切点(Pointcut)和通知(Advice) |
interfaces |
Class<?>[] |
代理需要实现的接口(JDK动态代理必须) |
proxyTargetClass |
boolean |
是否强制使用CGLIB代理(默认false ,即优先JDK动态代理) |
methodInterceptor |
MethodInterceptor |
环绕通知的最终执行体(通过Advisor组合后的实际调用链) |
1.1.2 代理类型的选择逻辑
Spring AOP选择JDK动态代理还是CGLIB代理的核心逻辑位于ProxyConfig
的isProxyTargetClass()
方法中:
java
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
- JDK动态代理触发条件 :目标类实现了至少一个接口,且
proxyTargetClass
为false
(默认)。 - CGLIB代理触发条件 :目标类未实现接口,或
proxyTargetClass
为true
(强制)。
1.2 JDK动态代理的底层实现:Proxy与InvocationHandler
JDK动态代理的核心是java.lang.reflect.Proxy
类和InvocationHandler
接口。Spring通过以下步骤生成代理对象:
1.2.1 生成代理类的字节码
当调用ProxyFactory.getProxy()
时,Spring会调用Proxy.newProxyInstance()
方法,该方法内部通过ProxyClassFactory
生成代理类的字节码。代理类的生成逻辑如下:
-
确定接口列表 :从
ProxyFactory
中获取目标类实现的接口(interfaces
)。 -
生成类名 :代理类的类名格式为
$Proxy + 随机数字
(如$Proxy0
),存储在JVM的方法区。 -
编写字节码 :通过
ProxyGenerator
生成代理类的字节码,核心逻辑包括:- 实现所有目标接口的方法。
- 每个方法内部调用
InvocationHandler.invoke()
方法,传递方法元数据和参数。 - 代理类的构造函数接收
InvocationHandler
实例作为参数。
1.2.2 代理对象的调用链路
当调用代理对象的方法时,实际执行流程如下(以SpringSelectProxyCase
中的proxyA1.foo()
为例):
scss
调用代理对象.foo()
→ 代理类的foo()方法(自动生成)
→ 调用InvocationHandler.invoke(invocation)
→ InvocationHandler.invoke()方法中:
→ 调用AdvisedSupport.applyAdvices()(应用切面逻辑)
→ 触发通知(前置、环绕等)
→ 通过MethodInvocation.proceed()调用目标方法
→ 返回目标方法结果
关键细节:
MethodInvocation
对象是Spring AOP的核心上下文载体,封装了目标方法(Method
)、目标对象(target
)、参数(arguments
)等信息。proceed()
方法的调用栈会触发后续的通知(如后置通知),若目标方法抛出异常,则触发异常通知。
1.3 CGLIB代理的底层实现:Enhancer与MethodInterceptor
CGLIB(Code Generation Library)通过ASM字节码库动态生成目标类的子类,其核心是Enhancer
类。Spring通过以下步骤生成CGLIB代理对象:
1.3.1 生成子类的字节码
当调用ProxyFactory.getProxy()
且proxyTargetClass
为true
时,Spring会使用CglibAopProxy
生成代理对象:
- 创建Enhancer实例 :设置目标类(
superclass
)为代理的父类。 - 设置回调(Callback) :将Spring的
MethodInterceptor
(包装了通知逻辑)设置为回调。 - 生成子类字节码 :通过ASM修改目标类的字节码,生成子类(类名格式为
目标类$$SpringCGLIB$$随机数字
)。 - 实例化子类:调用子类的无参构造函数创建代理对象。
1.3.2 代理对象的调用链路
当调用CGLIB代理对象的方法时,实际执行流程如下(以SpringSelectProxyCase
中的proxyB1.foo()
为例):
scss
调用代理对象.foo()
→ 代理类重写的foo()方法(CGLIB生成)
→ 调用MethodInterceptor.intercept(invocation)
→ MethodInterceptor.intercept()方法中:
→ 调用AdvisedSupport.applyAdvices()(应用切面逻辑)
→ 触发通知(前置、环绕等)
→ 通过MethodProxy.invokeSuper()调用父类(目标类)的方法
→ 返回目标方法结果
关键细节:
MethodProxy.invokeSuper()
通过ASM生成的MethodProxy
直接调用父类方法,避免了反射调用的性能开销(比Method.invoke()
快约10倍)。- CGLIB无法代理
final
方法(因为final
方法无法被重写)和private
方法(子类无法访问)。
1.4 代理类型验证:通过类名判断代理方式
通过打印代理对象的类名,可以直观判断Spring使用的代理类型:
java
// JDK动态代理对象类名(示例)
System.out.println(proxyA1.getClass().getName());
// 输出:com.sun.proxy.$Proxy0
// CGLIB代理对象类名(示例)
System.out.println(proxyB1.getClass().getName());
// 输出:com.dwl.select_proxy.SpringSelectProxyCase$SpringSelectProxyInterfaceB$$SpringCGLIB$$a1b2c3d4
二、通知的完整执行链路:以环绕通知为例
2.1 通知的类型与执行顺序
Spring AOP支持5种通知类型,其执行顺序(以方法调用target.foo()
为例)如下:
通知类型 | 执行时机 | 特点 |
---|---|---|
环绕通知 | 目标方法执行前后(完全控制) | 最强大,可阻止目标方法执行(不调用proceed() ) |
前置通知 | 目标方法执行前 | 无法获取目标方法返回值 |
后置通知 | 目标方法执行后(无论成功/失败) | 无法修改返回值 |
返回后通知 | 目标方法成功返回后 | 可获取返回值 |
异常后通知 | 目标方法抛出异常后 | 可获取异常信息 |
2.2 环绕通知的底层执行流程
以SpringSelectProxyCase
中的环绕通知为例:
java
// 1.2 定义通知(Advice):定义切面的具体行为(此处使用环绕通知)
MethodInterceptor interceptor = new MethodInterceptor() {
/**
* 环绕通知核心方法:控制目标方法的执行流程
* @param invocation 封装了目标方法调用的上下文信息(如目标对象、方法、参数等)
* @return 目标方法的返回值(或自定义返回值)
* @throws Throwable 目标方法抛出的异常
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("[切面] 前置处理:准备执行方法 [{}]", invocation.getMethod().getName());
long startTime = System.currentTimeMillis();
try {
// 调用目标方法(关键:通过invocation.proceed()触发目标方法执行)
Object result = invocation.proceed();
log.info("[切面] 后置处理(正常):方法 [{}] 执行成功,耗时:{}ms",
invocation.getMethod().getName(), System.currentTimeMillis() - startTime);
return result;
} catch (Throwable e) {
log.error("[切面] 后置处理(异常):方法 [{}] 执行失败,异常:{}",
invocation.getMethod().getName(), e.getMessage());
throw e; // 重新抛出异常,保证异常传递
}
}
};
执行链路详解:
-
前置处理阶段:
- 日志记录:"[切面] 前置处理:准备执行方法 foo"。
- 记录开始时间(
startTime
)。
-
目标方法调用:
- 调用
invocation.proceed()
,触发Spring AOP的通知链。 MethodInvocation.proceed()
会依次执行其他通知(如前置通知、后置通知),最终调用目标方法。
- 调用
-
正常返回阶段:
- 目标方法成功返回后,计算耗时(
System.currentTimeMillis() - startTime
)。 - 日志记录:"[切面] 后置处理(正常):方法 foo 执行成功,耗时:12ms"。
- 返回目标方法的返回值(若有)。
- 目标方法成功返回后,计算耗时(
-
异常处理阶段:
- 若目标方法抛出异常(如
RuntimeException
),proceed()
会抛出Throwable
。 - 捕获异常并记录错误日志:"[切面] 后置处理(异常):方法 foo 执行失败,异常:NullPointerException"。
- 重新抛出异常,确保调用方感知到错误。
- 若目标方法抛出异常(如
2.3 MethodInvocation的核心作用
MethodInvocation
是Spring AOP的"方法调用上下文",其核心方法包括:
方法名 | 作用描述 |
---|---|
getMethod() |
获取被调用的方法(Method 对象) |
getArguments() |
获取方法参数(Object[] ) |
getTarget() |
获取目标对象(被代理的对象) |
proceed() |
触发后续的通知和目标方法执行(可重复调用,但通常仅调用一次) |
setArguments() |
修改方法参数(需谨慎使用,可能影响目标方法行为) |
关键细节:
proceed()
方法的调用栈会触发所有后续通知(如多个切面的通知按顺序执行)。- 若
proceed()
未被调用(如环绕通知中遗漏),则目标方法永远不会执行。
三、切点匹配的底层原理:从AspectJ表达式到方法匹配
3.1 切点(Pointcut)的核心组件
Spring AOP的切点由Pointcut
接口定义,其核心功能是判断目标方法是否匹配切点规则 。实际使用中,AspectJExpressionPointcut
(基于AspectJ表达式)是最常用的实现类。
3.1.1 AspectJExpressionPointcut的解析流程
AspectJExpressionPointcut
的匹配流程分为以下步骤:
- 表达式解析 :将AspectJ表达式(如
execution(* com.dwl.select_proxy..*(..))
)解析为内部表示(AST
抽象语法树)。 - 方法匹配器生成 :根据解析后的AST生成
MethodMatcher
对象,用于实际的方法匹配。 - 方法匹配判断 :调用
MethodMatcher.matches(Method method, Class<?> targetClass)
判断方法是否匹配。
3.2 AspectJ表达式的语法与解析
AspectJ表达式是切点匹配的核心规则,其语法支持多种匹配模式:
表达式示例 | 匹配规则 |
---|---|
execution(* *(..)) |
匹配所有方法的执行(任意返回值、方法名、参数) |
execution(public * com..*.*(..)) |
匹配com 包及其子包下所有类的所有public方法 |
execution(* com.dwl.SelectProxyCase$SpringSelectProxyInterfaceA.foo(..)) |
精确匹配指定类的指定方法 |
@annotation(org.springframework.transaction.annotation.Transactional) |
匹配标注@Transactional 注解的方法 |
3.2.1 表达式解析的关键步骤
以execution(* com.dwl.select_proxy..*(..))
为例,解析流程如下:
- 词法分析 :将表达式字符串拆分为标记(Token),如
execution
、*
、com.dwl.select_proxy
、..
、*
、(..)
。 - 语法分析:根据AspectJ语法规则构建AST,验证表达式的合法性(如括号匹配、操作符正确性)。
- 语义分析:检查表达式中的类型是否有效(如包路径是否存在),并生成可执行的匹配逻辑。
3.3 方法匹配器(MethodMatcher)的匹配逻辑
MethodMatcher
接口定义了方法匹配的核心方法matches(Method method, Class<?> targetClass)
,其实现类根据切点类型不同而不同:
匹配器类型 | 匹配规则 | 典型应用场景 |
---|---|---|
StaticMethodMatcher |
静态匹配(仅基于方法签名,不考虑参数值) | execution 表达式 |
DynamicMethodMatcher |
动态匹配(基于方法签名和参数值) | args 表达式(匹配参数值) |
AnnotationMethodMatcher |
基于方法或类上的注解匹配 | @annotation 表达式 |
3.3.1 静态匹配器(StaticMethodMatcher)的匹配逻辑
以execution
表达式为例,StaticMethodMatcher
的匹配流程如下:
- 返回值匹配 :检查方法的返回值类型是否与表达式中的返回值模式匹配(
*
表示任意类型)。 - 方法名匹配 :检查方法名是否与表达式中的方法名模式匹配(精确匹配或
*
通配符)。 - 参数类型匹配 :检查方法的参数类型列表是否与表达式中的参数模式匹配(
..
表示任意数量/类型的参数)。
示例 :
表达式execution(* com.dwl.select_proxy.CutPointMatchingBeanA.foo(..))
匹配CutPointMatchingBeanA
类的foo
方法(任意返回值、任意参数)。
3.4 注解匹配器的底层实现:MergedAnnotations
@annotation
表达式通过MergedAnnotations
查找方法和类上的注解,其核心逻辑如下:
3.4.1 MergedAnnotations的查找策略
MergedAnnotations
是Spring对注解的"合并视图",支持以下查找策略:
策略 | 匹配范围 |
---|---|
Direct |
仅查找方法或类直接标注的注解(不包含继承的注解) |
Inherited |
仅查找类上的注解(包括父类继承的注解,仅适用于类注解) |
TypeHierarchy |
遍历类的继承链(父类→接口→父接口),查找所有层级的注解(默认策略) |
3.4.2 类继承层次的注解查找
以CutPointMatchingCase
中的CutPointMatchingBeanC
类为例(实现CutPointMatchingBean
接口,接口标注@Transactional
):
java
@Transaction
interface CutPointMatchingBean { void foo(); }
class CutPointMatchingBeanC implements CutPointMatchingBean {
@Override
public void foo() {}
}
当使用TypeHierarchy
策略查找CutPointMatchingBeanC
类的foo
方法注解时:
- 检查方法直接注解 :
foo
方法无直接注解(@Transactional
标注在接口上)。 - 检查类继承链 :
CutPointMatchingBeanC
类未标注@Transactional
。 - 检查接口继承链 :
CutPointMatchingBeanC
实现了CutPointMatchingBean
接口,接口标注了@Transactional
。 - 最终匹配 :由于
TypeHierarchy
策略包含接口的注解,因此foo
方法匹配@annotation(Transaction)
切入点。
3.5 自定义注解匹配器的实现
CutPointMatchingCase
中的HierarchicalClassAnnotationMatcher
演示了如何自定义注解匹配逻辑:
java
static class HierarchicalClassAnnotationMatcher extends StaticMethodMatcherPointcut {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 阶段1:检查方法直接标注的注解
MergedAnnotations methodAnnotations = MergedAnnotations.from(method);
if (methodAnnotations.isPresent(Transactional.class)) {
return true;
}
// 阶段2:检查类继承层次中的注解(父类、接口)
MergedAnnotations classAnnotations = MergedAnnotations.from(targetClass, SearchStrategy.TYPE_HIERARCHY);
return classAnnotations.isPresent(Transactional.class);
}
}
关键逻辑:
- 方法级优先:先检查方法本身是否有注解(直接存在或继承自方法注解)。
- 类继承层次 :若方法无注解,遍历类的父类和接口,查找是否存在注解(通过
SearchStrategy.TYPE_HIERARCHY
)。
四、代理类型对注解匹配的影响:JDK vs CGLIB
4.1 JDK动态代理与接口注解
JDK动态代理生成的代理类仅实现目标接口,因此:
- 接口注解可见:接口上的注解会被代理类保留(因为代理类实现了接口,接口的注解会被JVM保留)。
- 类注解不可见:目标类的类级注解不会被代理类继承(代理类是接口的实现,而非类的子类)。
示例验证:
java
@Transaction
interface CutPointMatchingBean { void foo(); }
class CutPointMatchingBeanImpl implements CutPointMatchingBean {
public void foo() {}
}
// JDK代理对象(接口类型)
CutPointMatchingBean proxy = (CutPointMatchingBean) proxyFactoryA.getProxy();
// 匹配结果:@annotation(Transaction)切入点会匹配proxy.foo(),因为接口有注解
4.2 CGLIB代理与类注解
CGLIB代理生成的是目标类的子类,因此:
- 类注解可见 :目标类的类级注解会被子类继承(通过
extends
关键字)。 - 接口注解不可见:目标类实现的接口的注解不会被代理类继承(除非显式重写接口方法并标注注解)。
示例验证:
java
class CutPointMatchingBeanImpl {
@Transaction
public void foo() {}
}
// CGLIB代理对象(子类类型)
CutPointMatchingBeanImpl proxy = (CutPointMatchingBeanImpl) proxyFactoryB.getProxy();
// 匹配结果:@annotation(Transaction)切入点会匹配proxy.foo(),因为父类有注解
4.3 混合场景:接口注解与类注解共存
若目标类同时实现接口并标注类级注解,代理类型会影响匹配结果:
代理类型 | 接口注解是否匹配 | 类注解是否匹配 | 示例场景 |
---|---|---|---|
JDK动态代理 | 是 | 否 | 代理类实现接口,接口注解可见 |
CGLIB代理 | 否 | 是 | 代理类是目标类的子类,类注解可见 |
五、总结与实践建议
5.1 核心结论总结
-
代理选择:
- JDK动态代理基于接口,适合接口驱动的场景(如RPC、DAO层)。
- CGLIB代理基于类继承,适合无接口或需要继承的场景(如Service层)。
- 强制使用CGLIB可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
配置。
-
通知执行:
- 环绕通知是最灵活的通知类型,需注意
proceed()
方法的调用位置(控制目标方法是否执行)。 - 通知的执行顺序:环绕(前)→ 前置 → 目标方法 → 后置(正常/异常)。
- 环绕通知是最灵活的通知类型,需注意
-
切点匹配:
- AspectJ表达式需精确匹配方法签名(返回值、方法名、参数类型)。
@annotation
切入点优先匹配方法级注解,类级注解需通过TypeHierarchy
策略查找继承链。- JDK代理仅匹配接口注解,CGLIB代理仅匹配类注解(默认场景)。
5.2 实践建议
-
明确代理类型:
- 若目标类实现接口,优先使用JDK动态代理(性能略优)。
- 若需代理类方法或强制使用类代理,启用
proxyTargetClass = true
。
-
优化切点表达式:
- 避免过度匹配(如
execution(* com..*.*(..))
),减少不必要的性能开销。 - 使用
@annotation
替代方法名匹配(提高可维护性)。
- 避免过度匹配(如
-
注意注解作用域:
- 若需类级注解生效,确保使用CGLIB代理或显式标注在方法上。
- 接口注解在JDK代理中可见,类注解在CGLIB代理中可见。
-
调试代理对象:
- 通过打印代理对象的类名(如
com.sun.proxy.$Proxy0
或$$SpringCGLIB$$
)验证代理类型。 - 使用
AopUtils.isJdkDynamicProxy(proxy)
和AopUtils.isCglibProxy(proxy)
工具类判断代理类型。
- 通过打印代理对象的类名(如
完整代码
java
package com.dwl.select_proxy;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
/**
* @ClassName SpringSelectProxyCase
* @Description 演示Spring AOP代理的创建与执行原理,包含JDK动态代理和CGLIB代理的对比验证
* 核心流程:定义业务接口/类 → 配置切点 → 定义通知 → 绑定切面 → 创建代理 → 验证代理行为
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class SpringSelectProxyCase {
/**
* 业务接口(JDK动态代理的基础:必须实现接口)
* 接口方法默认是public abstract修饰,实现类必须显式声明public(否则编译报错)
*/
interface SpringSelectProxyInterface {
void foo(); // 隐式public abstract
void bar(); // 隐式public abstract
}
/**
* 接口实现类A(实现SpringSelectProxyInterface接口)
* 用于演示JDK动态代理(基于接口的代理)
*/
static class SpringSelectProxyInterfaceA implements SpringSelectProxyInterface {
@Override
public void foo() { // 必须显式public(接口方法默认public abstract)
log.info("[目标对象A] 执行foo()方法"); // 调试日志:目标方法执行前标识
}
@Override
public void bar() { // 必须显式public
log.info("[目标对象A] 执行bar()方法");
}
}
/**
* 业务类B(未实现任何接口)
* 用于演示CGLIB代理(基于继承的代理)
*/
static class SpringSelectProxyInterfaceB {
public void foo() { // 显式public(普通类方法默认包私有,需显式public保证可访问)
log.info("[目标对象B] 执行foo()方法");
}
public void bar() { // 显式public
log.info("[目标对象B] 执行bar()方法");
}
}
public static void main(String[] args) {
/* ---------------------- 步骤1:配置AOP核心组件 ---------------------- */
// 1.1 定义切点(Pointcut):匹配需要拦截的方法
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/* 切点表达式说明:
/* - execution():匹配方法执行连接点
/* - *:返回值任意(*表示所有类型)
/* - com.dwl.select_proxy..*:包路径为com.dwl.select_proxy及其子包下的所有类(..表示任意子包和类)
/* - *(..):方法名任意(*),参数列表任意(..)
/* 最终匹配:com.dwl.select_proxy包及其子包下所有类的所有public方法(默认public,因Spring AOP仅支持public方法) */
String expression = "execution(* com.dwl.select_proxy..*(..))";
pointcut.setExpression(expression);
log.info("切点表达式已设置:{}", expression);
// 1.2 定义通知(Advice):定义切面的具体行为(此处使用环绕通知)
MethodInterceptor interceptor = new MethodInterceptor() {
/**
* 环绕通知核心方法:控制目标方法的执行流程
* @param invocation 封装了目标方法调用的上下文信息(如目标对象、方法、参数等)
* @return 目标方法的返回值(或自定义返回值)
* @throws Throwable 目标方法抛出的异常
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("[切面] 前置处理:准备执行方法 [{}]", invocation.getMethod().getName());
long startTime = System.currentTimeMillis();
try {
// 调用目标方法(关键:通过invocation.proceed()触发目标方法执行)
Object result = invocation.proceed();
log.info("[切面] 后置处理(正常):方法 [{}] 执行成功,耗时:{}ms",
invocation.getMethod().getName(), System.currentTimeMillis() - startTime);
return result;
} catch (Throwable e) {
log.error("[切面] 后置处理(异常):方法 [{}] 执行失败,异常:{}",
invocation.getMethod().getName(), e.getMessage());
throw e; // 重新抛出异常,保证异常传递
}
}
};
log.info("环绕通知已定义,包含前置/后置处理逻辑");
// 1.3 绑定切点和通知(Advisor):将切点与通知组合成完整的切面规则
Advisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
log.info("切面已绑定:切点[{}] + 通知[{}]", pointcut, interceptor.getClass().getName());
/* ---------------------- 步骤2:创建JDK动态代理对象(基于接口) ---------------------- */
log.info("\n===== 开始创建JDK动态代理对象 =====");
SpringSelectProxyInterfaceA targetA = new SpringSelectProxyInterfaceA();
// 2.1 初始化代理工厂(ProxyFactory核心作用:管理目标对象、切面,生成代理)
ProxyFactory proxyFactoryA = new ProxyFactory();
proxyFactoryA.setTarget(targetA); // 设置目标对象
proxyFactoryA.setInterfaces(targetA.getClass().getInterfaces()); // 设置目标对象实现的接口(JDK代理必须)
proxyFactoryA.addAdvisor(advisor); // 添加切面
// 2.2 查看代理类型(默认使用JDK动态代理)
// JDK动态代理原理:通过反射生成接口的匿名实现类($ProxyXXX),代理类实现目标接口并持有InvocationHandler
log.info("JDK代理默认是否启用CGLIB:{}", proxyFactoryA.isProxyTargetClass()); // 输出false(默认不启用)
// 2.3 获取代理对象(JDK动态代理)
SpringSelectProxyInterface proxyA1 = (SpringSelectProxyInterface) proxyFactoryA.getProxy();
log.info("JDK代理对象类型(未强制CGLIB): {}", proxyA1.getClass().getName());
// 输出类似:com.sun.proxy.$Proxy0(JDK动态代理生成的类名格式)
// 2.4 强制使用CGLIB(即使目标实现了接口)
proxyFactoryA.setProxyTargetClass(true); // 关键配置:强制使用CGLIB
SpringSelectProxyInterface proxyA2 = (SpringSelectProxyInterface) proxyFactoryA.getProxy();
log.info("JDK代理强制CGLIB后对象类型: {}", proxyA2.getClass().getName());
// 输出类似:com.dwl.select_proxy.SpringSelectProxyCase$SpringSelectProxyInterfaceA$$SpringCGLIB$$0(CGLIB代理类名格式)
/* ---------------------- 步骤3:验证JDK代理行为 ---------------------- */
log.info("\n===== 开始验证JDK代理对象 =====");
proxyA1.foo(); // 调用代理方法(触发切面逻辑+目标方法)
proxyA1.bar(); // 预期输出:切面前置→目标方法→切面后置
/* ---------------------- 步骤4:创建CGLIB代理对象(基于类继承) ---------------------- */
log.info("\n===== 开始创建CGLIB代理对象 =====");
SpringSelectProxyInterfaceB targetB = new SpringSelectProxyInterfaceB();
// 4.1 初始化代理工厂(CGLIB无需接口,但需设置目标类)
ProxyFactory proxyFactoryB = new ProxyFactory();
proxyFactoryB.setTarget(targetB); // 设置目标对象
// 注意:即使调用setInterfaces(目标类未实现接口),CGLIB仍会忽略接口,基于类生成代理
proxyFactoryB.setInterfaces(targetB.getClass().getInterfaces());
proxyFactoryB.addAdvisor(advisor); // 添加切面
// 4.2 查看代理类型(强制CGLIB,即使目标实现接口)
// CGLIB原理:通过字节码增强技术生成目标类的子类($$SpringCGLIB$$后缀),重写目标方法以插入切面逻辑
log.info("CGLIB代理默认是否启用CGLIB:{}", proxyFactoryB.isProxyTargetClass()); // 输出true(即使目标有接口)
// 4.3 获取代理对象(CGLIB代理)
SpringSelectProxyInterfaceB proxyB1 = (SpringSelectProxyInterfaceB) proxyFactoryB.getProxy();
log.info("CGLIB代理对象类型: {}", proxyB1.getClass().getName());
// 输出类似:com.dwl.select_proxy.SpringSelectProxyCase$SpringSelectProxyInterfaceB$$SpringCGLIB$$0
/* ---------------------- 步骤5:验证CGLIB代理行为 ---------------------- */
log.info("\n===== 开始验证CGLIB代理对象 =====");
proxyB1.foo(); // 调用代理方法(触发切面逻辑+目标方法)
proxyB1.bar(); // 预期输出:切面前置→目标方法→切面后置
}
}
package com.dwl.select_proxy;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
/**
* @ClassName CutPointMatchingCase
* @Description 深入解析Spring AOP切入点匹配原理的测试类
* 核心目标:验证不同切入点匹配策略(AspectJ表达式、方法注解、类继承层次注解)的底层匹配逻辑
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class CutPointMatchingCase {
/**
* 测试接口:类级别声明@Transactional(验证接口注解的继承规则)
* 底层原理:Java接口的方法默认是public abstract,注解标注在接口方法上时,
* 通过JDK动态代理生成的代理类会保留接口方法的注解(CGLIB代理则不会)
*/
@Transactional
interface CutPointMatchingBean {
void foo(); // 接口方法(默认public abstract)
void bar();
}
/**
* 测试类A:方法级别@Transactional(仅foo方法)
* 底层原理:@Transactional注解标注在方法上时,Spring AOP默认优先匹配方法级注解(优于类级)
* 方法注解的继承规则:Java注解默认不继承(@Inherited仅作用于类注解),因此子类重写的方法
* 不会自动继承父类方法上的@Transactional注解(除非显式重写并添加注解)
*/
static class CutPointMatchingBeanA {
@Transactional
public void foo() {
} // 方法级注解(直接存在)
public void bar() {
} // 无注解
}
/**
* 测试类B:类级别@Transactional(无方法注解)
* 底层原理:类级注解的匹配需要检查类的继承层次(父类、接口),Spring AOP通过
* MergedAnnotations.with(SearchStrategy.TYPE_HIERARCHY)实现层级查找
*/
static class CutPointMatchingBeanB {
public void foo() {
} // 无方法注解
}
/**
* 测试类C:实现带@Transactional的接口(验证接口注解的继承)
* 底层原理:当类实现接口时,Spring AOP在匹配接口方法时会检查接口上的注解(取决于代理类型)。
* 若使用JDK动态代理(基于接口),代理类会保留接口方法的注解;若使用CGLIB(基于类),
* 则不会自动继承接口注解(除非显式配置)。
*/
@Transactional // 类级别额外注解(可能与接口注解叠加)
static class CutPointMatchingBeanC implements CutPointMatchingBean {
@Override
public void foo() {
} // 实现接口方法(需检查接口注解)
@Override
public void bar() {
} // 实现接口方法
}
/**
* 测试1:AspectJ表达式切入点的底层匹配逻辑
* 核心原理:AspectJExpressionPointcut通过解析AspectJ表达式生成MethodMatcher,
* 匹配时通过反射获取方法元数据(返回值、参数类型、异常类型)与表达式规则对比
*/
private void testAspectJExpressionPointcut() {
log.info("===== 开始测试AspectJ表达式切入点 =====");
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/* 表达式解析:execution(* 包路径..类名.方法名(..))
* *:任意返回值;..:任意参数;包路径..:匹配任意子包
* */
pointcut.setExpression("execution(* com.dwl.select_proxy.CutPointMatchingCase$CutPointMatchingBeanA.foo(..))");
/* 匹配逻辑底层流程:
* 1. 解析表达式生成AspectJExpressionPointcut的内部表示(如MethodMatcher)
* 2. 调用matches方法时,通过反射获取目标方法的Method对象
* 3. 检查方法的返回值类型是否匹配表达式的返回值模式(*匹配任意)
* 4. 检查方法名是否完全匹配(foo)
* 5. 检查参数类型是否匹配(..匹配任意数量/类型的参数)
* 6. 最终返回是否完全匹配所有条件
*/
boolean fooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanA.class,
"foo",
"预期匹配(方法签名完全符合表达式)"
);
boolean barMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanA.class,
"bar",
"预期不匹配(方法名不符)"
);
log.info("AspectJ表达式匹配结果 - foo(): {}, bar(): {}", fooMatched, barMatched);
}
/**
* 测试2:@annotation注解切入点的底层匹配逻辑
* 核心原理:@annotation表达式通过MergedAnnotations直接查找方法上的注解,
* 其底层使用AnnotationUtils的findAnnotation方法,支持注解的继承(仅类注解)
*/
private void testAnnotationPointcut() {
log.info("\n===== 开始测试@annotation切入点 =====");
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/* 表达式解析:@annotation(注解全限定名)
* 匹配逻辑:检查方法或其类(含继承层次)是否存在该注解 */
pointcut.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
/* 底层关键步骤:
* 1. 获取目标方法的Method对象
* 2. 通过MergedAnnotations.from(method)查找方法上的注解(不包含类继承)
* 3. 若方法无注解,通过MergedAnnotations.from(targetClass)查找类上的注解(默认不包含接口)
* 4. Spring AOP默认优先匹配方法级注解(即使类上有同类型注解)
*/
boolean aFooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanA.class,
"foo",
"预期匹配(方法有@Transactional)"
);
boolean aBarMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanA.class,
"bar",
"预期不匹配(方法无@Transactional)"
);
boolean bFooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanB.class,
"foo",
"预期不匹配(类有注解但方法无)"
);
log.info("方法级注解匹配结果 - BeanA.foo(): {}, BeanA.bar(): {}, BeanB.foo(): {}",
aFooMatched, aBarMatched, bFooMatched);
}
/**
* 测试3:自定义类继承层次注解匹配器的底层逻辑
* 核心原理:通过MergedAnnotations.TYPE_HIERARCHY策略遍历类的继承链(父类→接口→父接口),
* 底层使用Class.getSuperclass()和Class.getInterfaces()递归查找注解
*/
private void testHierarchicalClassAnnotationMatcher() {
log.info("\n===== 开始测试类继承层次注解匹配 =====");
StaticMethodMatcherPointcut pointcut = new HierarchicalClassAnnotationMatcher();
/* 匹配逻辑底层流程:
* 1. 优先检查方法上的注解(MergedAnnotations.from(method))
* 2. 若方法无注解,检查类上的注解(MergedAnnotations.from(targetClass, TYPE_HIERARCHY))
* 3. 类注解查找范围包括:
* - 直接父类(递归直到Object)
* - 所有实现的接口(递归直到无父接口)
* 4. Spring AOP对代理类型的影响:
* - JDK动态代理:仅保留接口方法,因此接口注解会被匹配
* - CGLIB代理:保留父类方法,因此父类注解会被匹配
*/
boolean aFooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanA.class,
"foo",
"预期匹配(方法有@Transactional)"
);
boolean bFooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanB.class,
"foo",
"预期匹配(类有@Transactional)"
);
boolean cFooMatched = checkMethodMatch(
pointcut,
CutPointMatchingBeanC.class,
"foo",
"预期匹配(接口有@Transactional)"
);
boolean interfaceMethodMatched = checkMethodMatch(
pointcut,
CutPointMatchingBean.class,
"foo",
"预期匹配(接口自身有@Transactional)"
);
log.info("类继承层次匹配结果 - BeanA.foo(): {}, BeanB.foo(): {}, BeanC.foo(): {}, 接口方法: {}",
aFooMatched, bFooMatched, cFooMatched, interfaceMethodMatched);
}
/**
* 通用方法匹配检查工具(带底层反射原理说明)
*
* @param pointcut 切入点
* @param targetClass 目标类
* @param methodName 方法名
* @param assertDesc 断言说明
* @return 匹配结果
*/
private boolean checkMethodMatch(MethodMatcher pointcut, Class<?> targetClass,
String methodName, String assertDesc) {
try {
/*
* 反射获取方法元数据(底层通过Class.getMethod()实现)
* Class.getMethod(name, parameterTypes)会:
* 1. 检查方法是否存在(包括父类和接口)
* 2. 验证参数类型匹配
* 3. 返回public方法(包括父类public方法)
*/
Method method = targetClass.getMethod(methodName);
// 调用切入点的matches方法触发匹配逻辑
boolean matched = pointcut.matches(method, targetClass);
// 输出底层反射信息
log.info("\n===== 方法匹配详细分析 =====");
log.info("目标类:{}(类加载器:{})",
targetClass.getSimpleName(), targetClass.getClassLoader());
log.info("目标方法:{}(修饰符:{},返回值:{})",
method.getName(),
java.lang.reflect.Modifier.toString(method.getModifiers()),
method.getReturnType().getSimpleName());
log.info("断言说明:{}", assertDesc);
log.info("匹配结果:{}", matched);
return matched;
} catch (NoSuchMethodException e) {
log.error("获取方法失败:{}#{}, 原因:{}",
targetClass.getSimpleName(), methodName, e.getMessage());
return false;
}
}
/**
* 自定义切入点匹配器(深入MergedAnnotations底层实现)
* 核心原理:MergedAnnotations通过缓存和分层查找优化注解搜索效率,
* TYPE_HIERARCHY策略会遍历:
* - 当前类的注解(直接存在或继承自父类)
* - 所有实现的接口的注解(直接存在或继承自接口的父接口)
*/
static class HierarchicalClassAnnotationMatcher extends StaticMethodMatcherPointcut {
@Override
public boolean matches(@NonNull Method method, @NonNull Class<?> targetClass) {
log.info("\n===== 自定义匹配器详细匹配流程 =====");
log.info("开始匹配:类={}, 方法={}", targetClass.getSimpleName(), method.getName());
/* 阶段1:检查方法级注解(优先级最高)
* MergedAnnotations.from(method)的底层逻辑:
* 1. 获取方法的注解缓存(AnnotationCache)
* 2. 检查是否存在已缓存的注解信息
* 3. 若没有,通过反射获取方法的所有注解(method.getAnnotations())
* 4. 递归查找注解的元注解(@Inherited仅对类注解有效)
*/
MergedAnnotations methodAnnotations = MergedAnnotations.from(method);
boolean hasMethodAnnotation = methodAnnotations.isPresent(Transactional.class);
log.info("方法[{}]是否有@Transactional(直接注解):{}",
method.getName(), hasMethodAnnotation);
if (hasMethodAnnotation) {
log.info("匹配成功(方法直接标注@Transactional,优先级最高)");
return true;
}
/* 阶段2:检查类级注解(含继承层次)
* MergedAnnotations.from(targetClass, TYPE_HIERARCHY)的底层逻辑:
* 1. 创建TypeHierarchy类型的注解查找策略
* 2. 从当前类开始,向上遍历父类(直到Object)
* 3. 遍历当前类实现的所有接口(包括父接口)
* 4. 对每个类/接口,获取其注解并缓存
* 5. 最终合并所有找到的注解(类注解会覆盖接口注解?不,Spring AOP中方法和类注解是独立的)
*/
MergedAnnotations classAnnotations = MergedAnnotations.from(
targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
boolean hasClassAnnotation = classAnnotations.isPresent(Transactional.class);
log.info("类[{}](含继承层次)是否有@Transactional:{}",
targetClass.getSimpleName(), hasClassAnnotation);
if (hasClassAnnotation) {
log.info("匹配成功(类或其父类型标注@Transactional,类级注解生效)");
return true;
}
log.info("匹配失败(方法和类均无@Transactional)");
return false;
}
}
public static void main(String[] args) {
CutPointMatchingCase tester = new CutPointMatchingCase();
tester.testAspectJExpressionPointcut();
tester.testAnnotationPointcut();
tester.testHierarchicalClassAnnotationMatcher();
}
}