java:基于ASM的对Aspectj的JoinPoint参数封装技术解析

一、问题背景

在AOP框架实现中,我们可能需要对JoinPoint参数进行二次封装。

AocacheCtwClearTest.clearFalse()方法为例,AocacheCtwClearTestaocache的单元测试代码:

	@AoClear(methodName = "parseDateString",parameterTypes = {String.class,Class.class})
	private boolean clearFalse() {
		return false;
	}

如下是aocache进行静态织后的反编译后的java代码

    @AoClear(methodName="parseDateString", parameterTypes={String.class, Class.class})
    private boolean clearFalse() {
        boolean bl;
        JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart)ajc$tjp_4, (Object)this, (Object)this);
        boolean bl2 = false;
        boolean bl3 = bl = false;
        // 执行切面方法
        AocacheAnnotatedAspect.aspectOf().clearAfterReturning(joinPoint, Conversions.booleanObject((boolean)bl));
        return bl2;
    }

如下是经aocache进行静态织后的ASM指令代码:

  // access flags 0x2
  private clearFalse()Z
  @Lcom/gitee/l0km/aocache/annotations/AoClear;(methodName="parseDateString", parameterTypes={java.lang.String.class, java.lang.Class.class})
  ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
   L0
    GETSTATIC com/gitee/l0km/aocache/example/ctw/AocacheCtwClearTest.ajc$tjp_4 : Lorg/aspectj/lang/JoinPoint$StaticPart;
    ALOAD 0
    ALOAD 0
    INVOKESTATIC org/aspectj/runtime/reflect/Factory.makeJP (Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;
    ASTORE 1
   L1
    LINENUMBER 49 L1
    ICONST_0
    DUP
    ISTORE 2
    DUP
    ISTORE 3
    INVOKESTATIC com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.aspectOf ()Lcom/gitee/l0km/aocache/aop/AocacheAnnotatedAspect;
    ALOAD 1
    ILOAD 3
    INVOKESTATIC org/aspectj/runtime/internal/Conversions.booleanObject (Z)Ljava/lang/Object;
    INVOKEVIRTUAL com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.clearAfterReturning (Lorg/aspectj/lang/JoinPoint;Ljava/lang/Object;)V
    ILOAD 2
    IRETURN
   L2
    LOCALVARIABLE this Lcom/gitee/l0km/aocache/example/ctw/AocacheCtwClearTest; L0 L2 0
    MAXSTACK = 4
    MAXLOCALS = 4

因为AocacheAnnotatedAspect切面方法运行时实际需要的JoinPoint对象是com.gitee.l0km.aocache.aspectj.lang.JoingPoint,而不是org.aspectj.lang.JoingPoint,所以我们需要在上面静态织入的代码基础上,对于所有AocacheAnnotatedAspect切面方法调用时的JoinPoint参数插入对象封装指令,对JoinPoint参数进行处理,以达到对象转换的功能。

以上面的代码为例,我们希望clearFalse()最后的class改成这样:

    @AoClear(methodName="parseDateString", parameterTypes={String.class, Class.class})
    private boolean clearFalse() {
        boolean bl;
        org.aspectj.lang.JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart)ajc$tjp_4, (Object)this, (Object)this);
        boolean bl2 = false;
        boolean bl3 = bl = false;
        // 对joinPoint调用AspecjrtDecorator.wrap转换为com.gitee.l0km.aocache.aspectj.lang.JoingPoint,其他参数不变
        AocacheAnnotatedAspect.aspectOf().clearAfterReturning((JoinPoint)AspecjrtDecorator.wrap((Object)joinPoint), Conversions.booleanObject((boolean)bl));
        return bl2;
    }

二、技术原理推演

1.反编译代码分析

java调用方法时参数是从左到右入栈的,最右边参数被最后入栈。

对于非静态方法,第一个入栈的是调用方法所属的对象。然后从左到右依次将方法参数入栈。

以上面AocacheCtwClearTest.clearFalse()方法的反编译ASM指令为例,通过AocacheAnnotatedAspect.clearAfterReturning调用指令的分析可以推演这个过程:

  // access flags 0x2
  private clearFalse()Z
  @Lcom/gitee/l0km/aocache/annotations/AoClear;(methodName="parseDateString", parameterTypes={java.lang.String.class, java.lang.Class.class})
  ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
   L0
    GETSTATIC com/gitee/l0km/aocache/example/ctw/AocacheCtwClearTest.ajc$tjp_4 : Lorg/aspectj/lang/JoinPoint$StaticPart;
    ALOAD 0
    ALOAD 0
    INVOKESTATIC org/aspectj/runtime/reflect/Factory.makeJP (Lorg/aspectj/lang/JoinPoint$StaticPart;Ljava/lang/Object;Ljava/lang/Object;)Lorg/aspectj/lang/JoinPoint;
    // JoinPoint保存到局部变量1
    ASTORE 1
   L1
    LINENUMBER 49 L1
    ICONST_0
    DUP
    ISTORE 2
    DUP
    // false常量保存到局部变量3
    ISTORE 3
    // 1> 切面对象AocacheAnnotatedAspect入栈
    INVOKESTATIC com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.aspectOf ()Lcom/gitee/l0km/aocache/aop/AocacheAnnotatedAspect;
    // 2> 局部变量1,makeJP创建JoinPoint入栈
    ALOAD 1
    // false 局部变量3,用于booleanObject调用
    ILOAD 3
    INVOKESTATIC org/aspectj/runtime/internal/Conversions.booleanObject (Z)Ljava/lang/Object;
    // 3> 到这个位置最后入栈的是booleanObject调用的返回值
    INVOKEVIRTUAL com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.clearAfterReturning (Lorg/aspectj/lang/JoinPoint;Ljava/lang/Object;)V
    ILOAD 2
    IRETURN
   L2
    LOCALVARIABLE this Lcom/gitee/l0km/aocache/example/ctw/AocacheCtwClearTest; L0 L2 0
    MAXSTACK = 4
    MAXLOCALS = 4

INVOKEVIRTUAL AocacheAnnotatedAspect.clearAfterReturning 这条指令的时候,入栈的三个参数顺序分别是

  • 切面对象AocacheAnnotatedAspect
  • JoinPoint
  • booleanObject包装的false

正好匹配AocacheAnnotatedAspect.aspectOf().clearAfterReturning(joinPoint, Conversions.booleanObject((boolean)bl));

2.参数处理逻辑

所以要正确的AocacheAnnotatedAspect的切面方法调用指令的JoinPoint参数插入封装指令,需要按如下逻辑实现:

首先假设切面方法只会有且只有一个为JoinPoint或ProceedingJoinPoint的参数。 这个假设符合切面方法定义的要求

从右到左循环对切面方法的所有参数类型进行检查:

  • 如果不是JoinPoint类型,则将该参数从栈中退出保存到新定义的局部变量

    需要在org.objectweb.asm.commons.LocalVariablesSorter的帮助下创建局部变量。

  • 如果是JoinPoint类型,执行原来的插入代码逻辑并退出循环

退出循环后,恢复按从左到右的顺序,将保存在局部变量中的JoinPoint类型参数之后的所有参数重新入栈

3.流程图

感谢DeepSeek帮我画的流程图:

开始
分析切面方法,获取方法参数类型
遍历参数,从右到左
判断参数类型
参数类型不是JoinPoint
参数类型是JoinPoint
从栈中弹出参数,存储到局部变量
插入wrap方法调用,转换为自定义JoinPoint
退出循环
恢复栈:将保存的参数按从左到右的顺序重新入栈
生成修改后的ASM指令
结束

三、具体实现

1. 逆向参数处理

通过从右至左遍历方法参数,优先处理操作数栈顶元素:

		/**
		 * 从右到左出栈,存储右侧参数<br>
		 * 将出栈的参数存储到给定参数类型的局部变量,并将其插入到列表的头部。
		 *
		 * @param paramTypes  参数类型数组
		 * @param beforeIndex 指定的索引,表示在此索引之前的参数将不被存储
		 * @return 存储的局部变量列表
		 */
		private List<LocalVar> storePrams(Type[] paramTypes, int beforeIndex) {
			LinkedList<LocalVar> params = new LinkedList<>();
			for (int i = paramTypes.length - 1; i > beforeIndex; i--) {
				Type type = paramTypes[i];
				/** 创建新的局部变量 */
				int varIndex = localVariablesSorter.newLocal(type);
				super.visitVarInsn(type.getOpcode(Opcodes.ISTORE), varIndex);
				/** 存储参数,插入到列表头部 */
				params.add(0, new LocalVar(type, varIndex));
			}
			return params;
		}

2.插入wrap方法调用

		/**
		 * 插入包装JoinPoint参数的wrap方法调用指令,并将调用返回对象转换为重定位对象
		 * 
		 * @param argType 需要包装的JoinPoint参数类型,通过该参数确定目标装饰类型
		 */
		private void wrapJoinPointArgument(Type argType) {
			visitMethodInsn(Opcodes.INVOKESTATIC, 
					"com/gitee/l0km/aocache/AspecjrtDecorator",
					"wrap",
					"(Ljava/lang/Object;)Ljava/lang/Object;",
					false);
            // 强制类型转换为com/gitee/l0km/aocache/aspectj/lang/ProceedingJoinPoint
			visitTypeInsn(Opcodes.CHECKCAST, 
					relocatedOf(argType.getInternalName()));
		}

3. 操作数栈重组

处理完成后,将暂存的参数重新压入栈中:

		/**
		 * 从右到左出栈,存储右侧参数<br>
		 * 将出栈的参数存储到给定参数类型的局部变量,并将其插入到列表的头部。
		 *
		 * @param paramTypes  参数类型数组
		 * @param beforeIndex 指定的索引,表示在此索引之前的参数将不被存储
		 * @return 存储的局部变量列表
		 */
		private List<LocalVar> storePrams(Type[] paramTypes, int beforeIndex) {
			LinkedList<LocalVar> params = new LinkedList<>();
			for (int i = paramTypes.length - 1; i > beforeIndex; i--) {
				Type type = paramTypes[i];
				/** 创建新的局部变量 */
				int varIndex = localVariablesSorter.newLocal(type);
				super.visitVarInsn(type.getOpcode(Opcodes.ISTORE), varIndex);
				/** 存储参数,插入到列表头部 */
				params.add(0, new LocalVar(type, varIndex));
			}
			return params;
		}

四、实现效果对比

1.修改前反编译ASM代码:

    INVOKESTATIC com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.aspectOf ()Lcom/gitee/l0km/aocache/aop/AocacheAnnotatedAspect;
    ALOAD 1
    ILOAD 3
    INVOKESTATIC org/aspectj/runtime/internal/Conversions.booleanObject (Z)Ljava/lang/Object;
    INVOKEVIRTUAL com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.clearAfterReturning (Lorg/aspectj/lang/JoinPoint;Ljava/lang/Object;)V
    ILOAD 2
    IRETURN

2.修改后反编译ASM代码:

    INVOKESTATIC com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.aspectOf ()Lcom/gitee/l0km/aocache/aop/AocacheAnnotatedAspect;
    ALOAD 1
    ILOAD 3
    INVOKESTATIC org/aspectj/runtime/internal/Conversions.booleanObject (Z)Ljava/lang/Object;
    //<<<<<< 插入的指令【开始】
    ASTORE 5 // 保存booleanObject调用返回值到新的局部变量5
    INVOKESTATIC com/gitee/l0km/aocache/AspecjrtDecorator.wrap (Ljava/lang/Object;)Ljava/lang/Object;
    CHECKCAST com/gitee/l0km/aocache/aspectj/lang/JoinPoint
    ALOAD 5 // 局部变量5重新入栈
    //>>>>>> 插入的指令【结束】
    INVOKEVIRTUAL com/gitee/l0km/aocache/aop/AocacheAnnotatedAspect.clearAfterReturning (Lcom/gitee/l0km/aocache/aspectj/lang/JoinPoint;Ljava/lang/Object;)V
    ILOAD 2
    IRETURN

3.修改前反编译Java代码

    @AoClear(methodName="parseDateString", parameterTypes={String.class, Class.class})
    private boolean clearFalse() {
        boolean bl;
        JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart)ajc$tjp_4, (Object)this, (Object)this);
        boolean bl2 = false;
        boolean bl3 = bl = false;
        AocacheAnnotatedAspect.aspectOf().clearAfterReturning(joinPoint, Conversions.booleanObject((boolean)bl));
        return bl2;
    }

4.修改后反编译Java代码

    @AoClear(methodName="parseDateString", parameterTypes={String.class, Class.class})
    private boolean clearFalse() {
        boolean bl;
        org.aspectj.lang.JoinPoint joinPoint = Factory.makeJP((JoinPoint.StaticPart)ajc$tjp_4, (Object)this, (Object)this);
        boolean bl2 = false;
        boolean bl3 = bl = false;
        Object object = Conversions.booleanObject((boolean)bl);
        AocacheAnnotatedAspect.aspectOf().clearAfterReturning((JoinPoint)AspecjrtDecorator.wrap((Object)joinPoint), object);
        return bl2;
    }

五、应用场景

  1. AOP框架适配
    当需要将不同AOP框架的JoinPoint转换为统一接口时,例如将原生AspectJ的org.aspectj.lang.JoinPoint转换为项目自定义的com.gitee...JoinPoint

  2. 性能优化
    在缓存切面中自动包装参数实现键值生成:

  @AoCacheable(key="'user:'+#id")
  public User getUser(int id, JoinPoint jp) {
      // 自动包装jp参数用于键值生成
  }
  1. 依赖隔离
    通过字节码修改实现aspectjrt包名重定位,避免与项目中其他AOP框架产生依赖冲突。

六、技术优势

  1. 零侵入性:无需修改业务代码,编译期自动完成增强
  2. 兼容性强:支持JDK 6+环境,与Spring AOP共存

七、总结

通过正确的参数分析处理策略,实现了安全可靠的JoinPoint参数封装。该方案已在aocache-maven-plugin中验证,成功解决了多参数场景下的自动包装问题。

八、完整代码

指令插件的核心代码实现类:JoinPointWrapInjecter.java

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;

import com.gitee.l0km.aocache.AspecjrtDecorator;
import com.gitee.l0km.aocache.aop.AocacheAnnotatedAspect;

/**
 * <h3>JoinPoint包装注入器 - AOP字节码增强核心模块</h3>
 * 
 * <p>本模块通过ASM字节码操作技术,在编译期对切面方法进行增强,实现JoinPoint参数的自动包装和智能处理。</p>
 *
 * <h3>核心功能</h3>
 * <ul>
 *   <li>🔧 <b>动态参数包装</b>:自动识别并包装方法参数中的JoinPoint/ProceedingJoinPoint实例</li>
 *   <li>🔄 <b>上下文保持</b>:确保参数位置、类型、顺序与原始方法完全一致</li>
 *   <li>⚡ <b>性能优化</b>:采用最小变量分配策略,避免栈帧膨胀导致的性能损耗</li>
 *   <li>🛡️ <b>类型安全</b>:精确识别JoinPoint类型体系,避免错误包装非目标参数</li>
 * </ul>
 *
 * <h3>技术特性</h3>
 * <ul>
 *   <li>基于ASM 9.x字节码操作框架实现</li>
 *   <li>支持Spring AOP/AspectJ等多种切面编程模型</li>
 *   <li>采用逆向参数扫描算法提升处理效率</li>
 *   <li>内置线程安全的类型描述符重写机制</li>
 * </ul>
 *
 * <h3>工作原理</h3>
 * <p>通过方法访问器模式({@link MethodVisitor})对目标类进行以下增强:</p>
 * <ol>
 *   <li>扫描方法参数的JoinPoint类型</li>
 *   <li>动态分配局部变量暂存关联参数</li>
 *   <li>注入包装器实例和方法调用指令</li>
 *   <li>重构方法参数栈保持执行环境一致性</li>
 * </ol>
 *
 * <h3>使用场景</h3>
 * <ul>
 *   <li>需要增强JoinPoint的切面方法</li>
 *   <li>缓存注解的元数据扩展场景</li>
 *   <li>分布式跟踪的上下文传递需求</li>
 *   <li>需要方法拦截元数据扩展的AOP场景</li>
 * </ul>
 *
 * <h3>注意事项</h3>
 * <ul>
 *   <li>需配合{@link LocalVariablesSorter}使用以保证变量分配安全</li>
 *   <li>目标类需包含包装器类的静态引用</li>
 *   <li>建议在类加载期进行字节码增强</li>
 * </ul>
 *
 * @see org.objectweb.asm.ClassVisitor
 * @see org.objectweb.asm.commons.LocalVariablesSorter
 */
public class JoinPointWrapInjecter extends ClassVisitor {
	/**
	 * 表示 JoinPoint 类的类型常量。
	 * 该常量用于在字节码操作中标识 JoinPoint 类型。
	 */
	private static final Type JOINPOINT_TYPE = Type.getType(JoinPoint.class);
	/**
	 * 表示 ProceedingJoinPoint 类型的常量,用于在切面编程中处理连接点。
	 * 该类型用于获取方法执行的上下文信息。
	 */
	private static final Type PROCEEDJOINPOINT_TYPE = Type.getType(ProceedingJoinPoint.class);
	/**
	 * 表示目标切面类的内部类名常量,使用 AocacheAnnotatedAspect 类的类型信息获取。
	 * 该常量用于在切面注入过程中标识目标切面。
	 */
	private static final String TARGET_ASPECT = Type.getType(AocacheAnnotatedAspect.class).getInternalName();
	/**
	 * {@link  AspecjrtDecorator} 的内部类名常量
	 */
	private static final String DECORATOR_CLASS = Type.getType(AspecjrtDecorator.class).getInternalName();
	/**
	 * 表示用于包装方法({@link  AspecjrtDecorator#wrap(Object)})的名称。
	 */
	private static final String WRAP_METHOD = "wrap";
	/**
	 * 表示包装方法({@link  AspecjrtDecorator#wrap(Object)})的参数描述的常量字符串。
	 */
	private static final String WRAP_DESC;
	static {
		try {
			Method method = AspecjrtDecorator.class.getMethod(WRAP_METHOD, Object.class);
			WRAP_DESC = Type.getMethodDescriptor(method);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 表示原始包名的字符串。
	 */
	private final String originalPackage;
	/**
	 * 存储重定位包的名称。
	 */
	private final String relocatedPackage;
	private boolean ignored = true;

	/**
	 * 构造方法。
	 *
	 * @param cv               ClassVisitor 实例,用于访问类的结构。
	 * @param originalPackage  原始包名,用于标识类的原始位置。
	 * @param relocatedPackage 重定位包名,用于指定类的新位置。
	 */
	public JoinPointWrapInjecter(ClassVisitor cv, String originalPackage, String relocatedPackage) {
		super(Opcodes.ASM9, cv);
		this.originalPackage = originalPackage;
		this.relocatedPackage = relocatedPackage;
	}
	private String relocatedOf(String classname) {
		if (classname.startsWith(originalPackage)) {
			return relocatedPackage + classname.substring(originalPackage.length());
		}
		return classname;
	}
	/** 统一包名替换逻辑 */
	private String relocate(String input) {
		if (input != null) {
			return input.replace(originalPackage, relocatedPackage);
		}
		return input;
	}
	boolean isIgnored() {
		return ignored;
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
		// 构建访问器链
		LocalVariablesSorter lvs = new LocalVariablesSorter(access, desc, mv);
		return new AspectMethodVisitor(Opcodes.ASM9, lvs); 
	}
	/**
	 * AspectMethodVisitor 是用于实现 AOP 切面方法参数动态包装的 ASM 字节码访问器。
	 * 核心功能是在检测到切面方法调用时,对 JoinPoint 类型参数进行智能包装处理,实现透明的参数增强。
	 *
	 * <h3>实现原理</h3>
	 * 1. <b>参数定位机制</b>:<br>
	 *    - 通过方法描述符解析参数类型数组(Type[] paramTypes)<br>
	 *    - 使用 {@link #findLastJoinPointParam} 逆向扫描定位最后一个 JoinPoint 类型参数<br>
	 *
	 * 2. <b>参数暂存系统</b>:<br>
	 *    - 对目标参数右侧的非 JoinPoint 参数进行暂存({@link #storePrams(Type[], int)})<br>
	 *    - 使用 {@link LocalVariablesSorter} 分配新的局部变量槽位<br>
	 *    - 生成 ISTORE/ASTORE 等字节码指令保存参数值<br>
	 *
	 * 3. <b>动态包装处理</b>:<br>
	 *    - 调用 {@link #wrapJoinPointArgument(Type)} 生成包装逻辑:<br>
	 *      • 插入 GETSTATIC 获取包装器实例<br>
	 *      • 生成方法调用指令调用包装方法<br>
	 *    - 使用 ASM 的 visitMethodInsn 插入包装方法调用<br>
	 *
	 * 4. <b>参数恢复机制</b>:
	 *    - 通过 {@link #reloadParams(List)} 按 FILO 顺序恢复暂存参数<br>
	 *    - 生成 ILOAD/ALOAD 等字节码指令加载参数值<br>
	 *    - 保持原始参数顺序和类型一致性<br>
	 *
	 * <h3>核心功能</h3>
	 * ✅ JoinPoint 参数自动包装:将原始 JoinPoint 实例替换为增强后的包装实例<br>
	 * ✅ 参数位置无关处理:支持任意位置的 JoinPoint 参数(包括多参数场景)<br>
	 * ✅ 类型安全处理:精确识别 JoinPoint/ProceedingJoinPoint 及其子类<br>
	 * ✅ 字节码级优化:最小化局部变量使用,避免栈帧大小异常<br>
	 *
	 * <h3>典型处理流程(以方法参数列表 [A, B, JoinPoint, C] 为例)</h3>
	 * <ol>
	 *   <li>定位最后一个JoinPoint参数(索引=2)</li>
	 *   <li>暂存右侧参数C到局部变量</li>
	 *   <li>对索引2的参数执行包装:wrap(JoinPoint) → WrappedJoinPoint</li>
	 *   <li>重新加载暂存的参数C</li>
	 *   <li>生成新参数列表 [A, B, WrappedJoinPoint, C]</li>
	 * </ol>
	 *
	 * <h3>关键技术点</h3>
	 * <ul>
	 *   <li>逆向参数扫描策略:提高多JoinPoint参数场景的处理效率</li>
	 *   <li>局部变量动态分配:通过{@link LocalVariablesSorter}避免变量冲突</li>
	 *   <li>类型描述符重写:配合{@link JoinPointWrapInjecter#relocate}实现包路径适配</li>
	 *   <li>异常安全处理:确保字节码栈平衡不受包装逻辑影响</li>
	 * </ul>
	 *
	 * @see org.objectweb.asm.commons.LocalVariablesSorter
	 * @see com.gitee.l0km.aocache.plugin.JoinPointWrapInjecter
	 */
	private class AspectMethodVisitor extends MethodVisitor {
		private final LocalVariablesSorter localVariablesSorter;

		public AspectMethodVisitor(int api, MethodVisitor mv) {
			super(api, mv);
			this.localVariablesSorter = (LocalVariablesSorter) mv;
		}
		/**
		 * 处理方法调用指令,实现JoinPoint参数包装逻辑<br>
		 * 
		 * <p><b>处理逻辑:</b></p>
		 * <ul>
		 *   <li>识别目标切面方法调用({@link #TARGET_ASPECT})</li>
		 *   <li>执行JoinPoint参数包装预处理({@link #processJoinPointWrap})</li>
		 *   <li>重写方法描述符适配包路径({@link #relocate})</li>
		 * </ul>
		 *
		 * @param opcode 方法调用操作码(INVOKESTATIC/INVOKEVIRTUAL等)
		 * @param owner  目标类内部名称(ASM格式,如"com/example/ClassName")
		 * @param name   调用方法名称
		 * @param desc   方法描述符(包含参数和返回类型)
		 * @param itf    指示方法是否属于接口
		 */
		@Override
		public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
			// 检测AocacheAnnotatedAspect方法调用
			if (owner.equals(TARGET_ASPECT) && processJoinPointWrap(opcode, owner, name, desc)) {
				ignored = false;
				// 调用原始方法
				super.visitMethodInsn(opcode, owner, name, relocate(desc), itf);
			}else {
				super.visitMethodInsn(opcode, owner, name, desc, itf);
			}
		}

		/**
		 * 处理切面方法中JoinPoint参数的包装逻辑。<br>
		 *
		 * 实现步骤:
		 * 1. 从参数列表中从右向左查找最后一个JoinPoint/ProceedingJoinPoint类型参数的位置
		 * 2. 将该参数右侧的所有非JoinPoint参数暂存到局部变量中
		 * 3. 对找到的JoinPoint参数执行包装逻辑(调用AspecjrtDecorator.wrap方法)
		 * 4. 将暂存的参数按原始顺序重新加载到栈中
		 * 
		 * @param opcode 方法调用操作码(INVOKESTATIC/INVOKEVIRTUAL等)
		 * @param owner  目标方法所属类的内部名称(如"com/example/ClassName")
		 * @param name   目标方法名称
		 * @param desc   方法描述符,格式为"(参数类型)返回类型",例如:"(Ljava/lang/String;I)V"
		 * @return 当方法参数中包含JoinPoint/ProceedingJoinPoint类型参数时返回true,否则返回false
		 * 
		 * @see #findLastJoinPointParam(Type[])  用于定位JoinPoint参数的辅助方法
		 * @see #storePrams(Type[], int)        用于参数暂存的辅助方法
		 * @see #wrapJoinPointArgument(Type)    执行实际包装操作的辅助方法
		 * @see #reloadParams(List)             用于参数恢复的辅助方法
		 */
		private boolean processJoinPointWrap(int opcode, String owner,
				String name, String desc) {
			Type[] paramTypes = Type.getArgumentTypes(desc);
			int joinPointIndex = findLastJoinPointParam(paramTypes);
			if (joinPointIndex == -1) {
				return false;
			}

			List<LocalVar> savedParams = storePrams(paramTypes, joinPointIndex);

			wrapJoinPointArgument(paramTypes[joinPointIndex]);

			reloadParams(savedParams);

			return true;
		}
		/**
		 * 插入包装JoinPoint参数的wrap方法调用指令,并将调用返回对象转换为重定位对象
		 * 
		 * @param argType 需要包装的JoinPoint参数类型,通过该参数确定目标装饰类型
		 */
		private void wrapJoinPointArgument(Type argType) {
		    /* 调用静态方法完成参数包装:
		     * 1. 通过INVOKESTATIC调用装饰器类的包装方法
		     * 2. 使用预定义的装饰器类名、方法名和方法描述符
		     */
		    visitMethodInsn(Opcodes.INVOKESTATIC, 
		            DECORATOR_CLASS,
		            WRAP_METHOD,
		            WRAP_DESC,
		            false);
		    
		    /* 执行类型转换确保对象类型正确:
		     * 1. CHECKCAST指令转换对象类型
		     * 2. 获取参数类型重定位后的内部名称
		     */
		    visitTypeInsn(Opcodes.CHECKCAST, 
		            relocatedOf(argType.getInternalName()));
		}
		private int findLastJoinPointParam(Type[] paramTypes) {
			for (int i = paramTypes.length - 1; i >= 0; i--) {
				if (paramTypes[i].equals(JOINPOINT_TYPE) || paramTypes[i].equals(PROCEEDJOINPOINT_TYPE)) {
					return i;
				}
			}
			return -1;
		}

		/**
		 * 从右到左出栈,存储右侧参数<br>
		 * 将出栈的参数存储到给定参数类型的局部变量,并将其插入到列表的头部。
		 *
		 * @param paramTypes  参数类型数组
		 * @param beforeIndex 指定的索引,表示在此索引之前的参数将不被存储
		 * @return 存储的局部变量列表
		 */
		private List<LocalVar> storePrams(Type[] paramTypes, int beforeIndex) {
			LinkedList<LocalVar> params = new LinkedList<>();
			for (int i = paramTypes.length - 1; i > beforeIndex; i--) {
				Type type = paramTypes[i];
				/** 创建新的局部变量 */
				int varIndex = localVariablesSorter.newLocal(type);
				super.visitVarInsn(type.getOpcode(Opcodes.ISTORE), varIndex);
				/** 存储参数,插入到列表头部 */
				params.add(0, new LocalVar(type, varIndex));
			}
			return params;
		}

		/**
		 * 从左到右入栈,恢复右侧参数<br>
		 * 
		 * @param params 包含要重新加载的局部变量的列表
		 */
		private void reloadParams(List<LocalVar> params) {
			for (LocalVar var : params) {
				super.visitVarInsn(var.type.getOpcode(Opcodes.ILOAD), var.index);
			}
		}
		private class LocalVar {
			final Type type;
			final int index;
			LocalVar(Type type, int index) {
				this.type = type;
				this.index = index;
			}
		}
	}
}

完整代码参见码云仓库:https://gitee.com/l0km/aocache

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值