一、问题背景
在AOP框架实现中,我们可能需要对JoinPoint参数进行二次封装。
以AocacheCtwClearTest.clearFalse()
方法为例,AocacheCtwClearTest是aocache的单元测试代码:
@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帮我画的流程图:
三、具体实现
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;
}
五、应用场景
-
AOP框架适配
当需要将不同AOP框架的JoinPoint转换为统一接口时,例如将原生AspectJ的org.aspectj.lang.JoinPoint
转换为项目自定义的com.gitee...JoinPoint
。 -
性能优化
在缓存切面中自动包装参数实现键值生成:
@AoCacheable(key="'user:'+#id")
public User getUser(int id, JoinPoint jp) {
// 自动包装jp参数用于键值生成
}
- 依赖隔离
通过字节码修改实现aspectjrt包名重定位,避免与项目中其他AOP框架产生依赖冲突。
六、技术优势
- 零侵入性:无需修改业务代码,编译期自动完成增强
- 兼容性强:支持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