Spring AOP:从代理创建到切点匹配

引言

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代理的核心逻辑位于ProxyConfigisProxyTargetClass()方法中:

java 复制代码
public void setProxyTargetClass(boolean proxyTargetClass) {  
    this.proxyTargetClass = proxyTargetClass;  
}
  • JDK动态代理触发条件 :目标类实现了至少一个接口,且proxyTargetClassfalse(默认)。
  • CGLIB代理触发条件 :目标类未实现接口,或proxyTargetClasstrue(强制)。

1.2 JDK动态代理的底层实现:Proxy与InvocationHandler

JDK动态代理的核心是java.lang.reflect.Proxy类和InvocationHandler接口。Spring通过以下步骤生成代理对象:

1.2.1 生成代理类的字节码

当调用ProxyFactory.getProxy()时,Spring会调用Proxy.newProxyInstance()方法,该方法内部通过ProxyClassFactory生成代理类的字节码。代理类的生成逻辑如下:

  1. 确定接口列表 ​:从ProxyFactory中获取目标类实现的接口(interfaces)。

  2. 生成类名 ​:代理类的类名格式为$Proxy + 随机数字(如$Proxy0),存储在JVM的方法区。

  3. 编写字节码 ​:通过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()proxyTargetClasstrue时,Spring会使用CglibAopProxy生成代理对象:

  1. 创建Enhancer实例 :设置目标类(superclass)为代理的父类。
  2. 设置回调(Callback)​ :将Spring的MethodInterceptor(包装了通知逻辑)设置为回调。
  3. 生成子类字节码 :通过ASM修改目标类的字节码,生成子类(类名格式为目标类$$SpringCGLIB$$随机数字)。
  4. 实例化子类:调用子类的无参构造函数创建代理对象。

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; // 重新抛出异常,保证异常传递
        }
    }
};

执行链路详解​:

  1. 前置处理阶段​:

    • 日志记录:"[切面] 前置处理:准备执行方法 foo"。
    • 记录开始时间(startTime)。
  2. 目标方法调用​:

    • 调用invocation.proceed(),触发Spring AOP的通知链。
    • MethodInvocation.proceed()会依次执行其他通知(如前置通知、后置通知),最终调用目标方法。
  3. 正常返回阶段​:

    • 目标方法成功返回后,计算耗时(System.currentTimeMillis() - startTime)。
    • 日志记录:"[切面] 后置处理(正常):方法 foo 执行成功,耗时:12ms"。
    • 返回目标方法的返回值(若有)。
  4. 异常处理阶段​:

    • 若目标方法抛出异常(如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的匹配流程分为以下步骤:

  1. 表达式解析 :将AspectJ表达式(如execution(* com.dwl.select_proxy..*(..)))解析为内部表示(AST抽象语法树)。
  2. 方法匹配器生成 :根据解析后的AST生成MethodMatcher对象,用于实际的方法匹配。
  3. 方法匹配判断 :调用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..*(..))为例,解析流程如下:

  1. 词法分析 :将表达式字符串拆分为标记(Token),如execution*com.dwl.select_proxy..*(..)
  2. 语法分析:根据AspectJ语法规则构建AST,验证表达式的合法性(如括号匹配、操作符正确性)。
  3. 语义分析:检查表达式中的类型是否有效(如包路径是否存在),并生成可执行的匹配逻辑。

3.3 方法匹配器(MethodMatcher)的匹配逻辑

MethodMatcher接口定义了方法匹配的核心方法matches(Method method, Class<?> targetClass),其实现类根据切点类型不同而不同:

匹配器类型 匹配规则 典型应用场景
StaticMethodMatcher 静态匹配(仅基于方法签名,不考虑参数值) execution表达式
DynamicMethodMatcher 动态匹配(基于方法签名和参数值) args表达式(匹配参数值)
AnnotationMethodMatcher 基于方法或类上的注解匹配 @annotation表达式

3.3.1 静态匹配器(StaticMethodMatcher)的匹配逻辑

execution表达式为例,StaticMethodMatcher的匹配流程如下:

  1. 返回值匹配 :检查方法的返回值类型是否与表达式中的返回值模式匹配(*表示任意类型)。
  2. 方法名匹配 :检查方法名是否与表达式中的方法名模式匹配(精确匹配或*通配符)。
  3. 参数类型匹配 :检查方法的参数类型列表是否与表达式中的参数模式匹配(..表示任意数量/类型的参数)。

示例 ​:

表达式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方法注解时:

  1. 检查方法直接注解foo方法无直接注解(@Transactional标注在接口上)。
  2. 检查类继承链CutPointMatchingBeanC类未标注@Transactional
  3. 检查接口继承链CutPointMatchingBeanC实现了CutPointMatchingBean接口,接口标注了@Transactional
  4. 最终匹配 :由于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 核心结论总结

  1. 代理选择​:

    • JDK动态代理基于接口,适合接口驱动的场景(如RPC、DAO层)。
    • CGLIB代理基于类继承,适合无接口或需要继承的场景(如Service层)。
    • 强制使用CGLIB可通过@EnableAspectJAutoProxy(proxyTargetClass = true)配置。
  2. 通知执行​:

    • 环绕通知是最灵活的通知类型,需注意proceed()方法的调用位置(控制目标方法是否执行)。
    • 通知的执行顺序:环绕(前)→ 前置 → 目标方法 → 后置(正常/异常)。
  3. 切点匹配​:

    • AspectJ表达式需精确匹配方法签名(返回值、方法名、参数类型)。
    • @annotation切入点优先匹配方法级注解,类级注解需通过TypeHierarchy策略查找继承链。
    • JDK代理仅匹配接口注解,CGLIB代理仅匹配类注解(默认场景)。

5.2 实践建议

  1. 明确代理类型​:

    • 若目标类实现接口,优先使用JDK动态代理(性能略优)。
    • 若需代理类方法或强制使用类代理,启用proxyTargetClass = true
  2. 优化切点表达式​:

    • 避免过度匹配(如execution(* com..*.*(..))),减少不必要的性能开销。
    • 使用@annotation替代方法名匹配(提高可维护性)。
  3. 注意注解作用域​:

    • 若需类级注解生效,确保使用CGLIB代理或显式标注在方法上。
    • 接口注解在JDK代理中可见,类注解在CGLIB代理中可见。
  4. 调试代理对象​:

    • 通过打印代理对象的类名(如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();
    }

}
相关推荐
ZeroNews内网穿透6 分钟前
服装零售企业跨区域运营难题破解方案
java·大数据·运维·服务器·数据库·tcp/ip·零售
用户81221993672220 分钟前
C# .Net Core零基础从入门到精通实战教程全集【190课】
后端
bobz96522 分钟前
FROM scratch: docker 构建方式分析
后端
sleepcattt24 分钟前
Spring中Bean的实例化(xml)
xml·java·spring
lzzy_lx_208942 分钟前
Spring Boot登录认证实现学习心得:从皮肤信息系统项目中学到的经验
java·spring boot·后端
Dcs43 分钟前
立即卸载这些插件,别让它们偷你的资产!
java
小七mod1 小时前
【Spring】Java SPI机制及Spring Boot使用实例
java·spring boot·spring·spi·双亲委派
前端付豪1 小时前
21、用 Python + Pillow 实现「朋友圈海报图生成器」📸(图文合成 + 多模板 + 自动换行)
后端·python
亿.61 小时前
【Java安全】RMI基础
java·安全·ctf·rmi
ruan1145142 小时前
Java Lambda 类型推断详解:filter() 方法与 Predicate<? super T>
java·开发语言·spring·stream