字节码增强,实质就是在编译期或运行期进行<font color="#D46B08">字节码插桩</font>,以便在运行期影响程序的<font color="#D46B08">执行行为</font>。按照增强时机,可以分为<font color="red">编译时增强</font>(Pluggable Annotation Processing),<font color="red">运行时增强</font>(代理类,java-agent)。
实现方式:
asm,Javassist, cglib,java-proxy,bytebuddy
字节码工具 | java-proxy | asm | Javassist | cglib | bytebuddy |
---|---|---|---|---|---|
类创建 | 支持 | 支持 | 支持 | 支持 | 支持 |
实现接口 | 支持 | 支持 | 支持 | 支持 | 支持 |
方法调用 | 支持 | 支持 | 支持 | 支持 | 支持 |
类扩展 | 不支持 | 支持 | 支持 | 支持 | 支持 |
父类方法调用 | 不支持 | 支持 | 支持 | 支持 | 支持 |
优点 | 容易上手,<font color="red">简单动态代理首选</font> | <font color="red">任意字节码插入,几乎不受限制</font> | <font color="red">java原始语法,字符串形式插入,写入直观 </font> | bytebuddy看起来差不多 | <font color="red">支持任意维度的拦截,可以获取原始类、方法,以及代理类和全部参数</font> |
缺点 | 功能有限,不支持扩展 | 学习难度大,编写代码量大 | 不支持jdk1.5以上的语法,如泛型,增强for | 正在被bytebuddy淘汰 | 不太直观,学习理解有些成本,API非常多 |
常见应用 | spring-aop,MyBatis | cglib,bytebuddy | Fastjson,MyBatis | spring-aop,EasyMock,jackson-databind | SkyWalking,Mockito,Hibernate,powermock |
学习成本 | <font color="#389E0D">一星</font> | <font color="red">五星</font> | <font color="#389E0D">二星</font> | <font color="#D46B08">三星</font> | <font color="#D46B08">三星</font> |
使用场景:
由于字节码增强可以在完全不侵入业务代码的情况下植入代码逻辑,常见的场景:
- <font color="red">动态代理;</font>
- <font color="red">热部署;</font>
- <font color="red">调用链跟踪埋点;</font>
- <font color="red">动态插入log(性能监控);</font>
- <font color="red">测试代码覆盖率跟踪;</font>
java-proxy介绍
java动态代理基于InvocationHandler接口,代码由ProxyGenerator.generateProxyClass生成,简单直观。
<font color="red">生成的Proxy实现接口全部方法,内部调用InvocationHandler的invoke方法</font>
例子
动物咖啡接口
public interface AnimalCoffee {
Object make();
}
public interface CoffeeNameAware {
String getName();
}
生成一个原味咖啡
AnimalCoffee original = (AnimalCoffee) Proxy.newProxyInstance(AnimalCoffee.class.getClassLoader(), new Class<?>[]{AnimalCoffee.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("产地", "中国");
result.put("价格", "10¥");
return result;
}
});
原味咖啡生成的字节码
//将hashCode,toString,equals方法忽略
public final class $Proxy4 extends Proxy implements AnimalCoffee {
private static Method m3;
public $Proxy4(InvocationHandler var1) throws {
super(var1);
}
public final Object make() throws {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m3 = Class.forName("AnimalCoffee").getMethod("make");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
ASM介绍
asm插入字节码是原始字节码语法,上手难度高,原始语法不受框架限制,任意插入字节码;
ASM API 是基于 ClassVisitor 抽象类的。这 个 类中的每个方法都对应于同名的类文件结构部分。简单的部分只需一个方法调 用就能 访问,这个调用返回 void,其参数描述了这些部分的内容。有些部分的内容可以达到 任意长度、 任意复杂度,这样的部分可以用一个初始方法调用来访问,返回一个辅助的访问者 类。 visitAnnotation、visitField 和 visitMethod 方法就是这种情况,它们分别返 回 AnnotationVisitor、FieldVisitor 和 MethodVisitor.
ClassReader (读取)→ ClassVisitor(链式修改)→ ClassWriter → (保存写入)
//javap -v 查看
getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
打印类字节码
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
生成类-原味咖啡
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "OriginalAnimalCoffee", null, "java/lang/Object", new String[]{"AnimalCoffee"});
classWriter.visitSource("OriginalAnimalCoffee.java", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(12, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "make", "()Ljava/lang/Object;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(15, label0);
methodVisitor.visitTypeInsn(NEW, "java/util/HashMap");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false);
methodVisitor.visitVarInsn(ASTORE, 1);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(16, label1);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u53e3\u5473");
methodVisitor.visitLdcInsn("\u539f\u5473");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(17, label2);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ea7\u5730");
methodVisitor.visitLdcInsn("\u4e2d\u56fd");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(18, label3);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ef7\u683c");
methodVisitor.visitLdcInsn("10\u00a5");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(19, label4);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(ARETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("result", "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;", label1, label5, 1);
methodVisitor.visitMaxs(3, 2);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
byte[] classByte = classWriter.toByteArray();
转换(修改)类
byte[] oldCl = oldClassByte;
ClassReader cr = new ClassReader(oldCl); //读取
ClassWriter cw = new ClassWriter(0);
cr.accept(cw, 0); //将old写入cw
//新增一个字段
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
byte[] newCl = cw.toByteArray();
//引入一个 ClassVisitor:
ClassReader cr = new ClassReader(oldCl);
ClassWriter cw = new ClassWriter(0);
// cv 将所有事件转发给 cw
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
cr.accept(cv, 0); //将old写入cv
byte[] newCl2 = cw.toByteArray();
<font color="red">给出了与上述代码相对应的体系结构,其中的组件用方框表示,事件用箭头表示(其中的垂直时间线与程序图中一样)。</font>
1649748593820-14ef8d6f-d376-415b-9341-b4de50327291.png
转换链
public class ChangeVersionAdapter extends ClassVisitor {
public ChangeVersionAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
}
// 和前面相比,这里是互相拥有,前面是单一拥有
byte[] b1 = oldClassByte;
ClassReader cr = new ClassReader(b1);
ClassWriter cw = new ClassWriter(cr, 0); ChangeVersionAdapter ca = new ChangeVersionAdapter(cw); cr.accept(ca, 0);
byte[] b2 = cw.toByteArray();
jdk-agent: instrument应用
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader l, String name, Class c,
ProtectionDomain d, byte[] b)
throws IllegalClassFormatException {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ChangeVersionAdapter(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
});
}
移除类成员
用于转换类版本的方法当然也可用于 ClassVisitor 类的其他方法。例如,通过 改 变 visitField 和 visitMethod 方法的 access 或 name 参数,可以改变一个字段 或一个方 法的修饰字段或名字。另外,除了在转发的方法调用中使用经过修改的参数之外,还 可以选择根 本不转发该调用。其效果就是相应的类元素被移除。
public class RemoveMethodAdapter extends ClassVisitor {
private String mName;
private String mDesc;
public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) {
super(ASM4, cv);
this.mName = mName;
this.mDesc = mDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// 不要委托至下一个访问器 -> 这样将移除该方
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
javassist介绍
Javassit相比于ASM要简单点,Javassit提供了更高级的API,当时执行效率上比ASM要差,因为ASM上直接操作的字节码。
工作步骤
bytecode (1).pngClassPool容器
- getDefault (): 返回默认的ClassPool ,单例模式,一般通过该方法创建我们的ClassPool;
- appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类问题;
- importPackage(String packageName):导入包;
- makeClass(String classname):创建一个空类,没有变量和方法,后序通过CtClass的函数进行添加;
- get(String classname)、getCtClass(String classname) : 根据类路径名获取该类的CtClass对象,用于后续的编辑。
CtClass类
- debugDump;String类型,如果生成。class文件,保存在这个目录下。
- setName(String name):给类重命名;
- setSuperclass(CtClass clazz):设置父类;
- addField(CtField f, Initializer init):添加字段(属性),初始值见CtField;
- addMethod(CtMethod m):添加方法(函数);
- toBytecode(): 返回修改后的字节码。需要注意的是一旦调用该方法,则无法继续修改CtClass;
- toClass(): 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的CtClass;
- writeFile(String directoryName):根据CtClass生成 .class 文件;
- defrost():解冻类,用于使用了toclass()、toBytecode、writeFile(),类已经被JVM加载,Javassist冻结CtClass后;
- detach():避免内存溢出,从ClassPool中移除一些不需要的CtClass。
Loader类加载器
- loadClass(String name):加载类
CtField字段
- CtField(CtClass type, String name, CtClass declaring) :构造函数,添加字段类型,名称,所属的类;
- CtField.Initializer constant():CtClass使用addField时初始值的设置;
- setModifiers(int mod):设置访问级别,一般使用Modifier调用常量。
CtMethod方法
- insertBefore(String src):在方法的起始位置插入代码;
- insertAfter(String src):在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
- insertAt(int lineNum, String src):在指定的位置插入代码;
- addCatch(String src, CtClass exceptionType):将方法内语句作为try的代码块,插入catch代码块src;
- setBody(String src):将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
- setModifiers(int mod):设置访问级别,一般使用Modifier调用常量;
- invoke(Object obj, Object... args):反射调用字节码生成类的方法。
//对于setBody $0代表this $1、$2、...代表方法的第几个参数
setBody("{$0.name = $1;}");
$符号含义
符号 | 含义 |
---|---|
1, $2, ... | this,第几个参数 |
$args | 参数列表. $args的类型是Object[]. |
$$ | 所有实参.例如, m($$) 等价于 m(2,...) |
$cflow(...) | cflow变量 |
$r | 结果类型. 用于表达式转换. |
$w | 包装类型. 用于表达式转换. |
$_ | 结果值 |
$sig | java.lang.Class列表,代表正式入参类型 |
$type | java.lang.Class对象,代表正式入参值. |
$class | java.lang.Class对象,代表传入的代码段. |
生成类-原味咖啡
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage("java.util.Map");
classPool.importPackage("java.util.HashMap");
CtClass ctClass = classPool.makeClass("OriginalAnimalCoffee");
ctClass.addInterface(classPool.get("AnimalCoffee"));
ctClass.addMethod(CtMethod.make("public Object make() {\n" +
" Map result = new HashMap();\n" +
" result.put(\"口味\", \"原味\");\n" +
" result.put(\"产地\", \"中国\");\n" +
" result.put(\"价格\", \"10¥\");\n" +
" return result;\n" +
" }", ctClass));
byte[] classByte = ctClass.toBytecode();
ctClass.detach();
javassist常见错误
for (Header header : headers) {}
//高级语法导致,换成1.5jdk的循环
for (int i = 0; i < headers.length; i++){}
javassist.CannotCompileException: [source error] ; is missing
toParams(String query, Map<String, Object> params){}
//泛型语法导致,换成1.5jdk的
toParams(String query, Map params){}
javassist.CannotCompileException: [source error] syntax error near "ery, Map<String, Obj"
pool.importPackage("java.util.ArrayList");
//引入依赖包
javassist.CannotCompileException: [source error] no such class: ArrayList
bytebuddy介绍
常用核心API
ByteBuddy
- 流式API方式的入口类
- 提供Subclassing/Redefining/Rebasing方式改写字节码
- 所有的操作依赖DynamicType.Builder进行,创建不可变的对象
ElementMatchers(ElementMatcher)
- 提供一系列的元素匹配的工具类(named/any/nameEndsWith等等)
- ElementMatcher(提供对类型、方法、字段、注解进行matches的方式,类似于Predicate)
- Junction对多个ElementMatcher进行了and/or操作
DynamicType(动态类型,所有字节码操作的开始,非常值得关注)
- Unloaded(动态创建的字节码还未加载进入到虚拟机,需要类加载器进行加载)
- Loaded(已加载到jvm中后,解析出Class表示)
- Default(DynamicType的默认实现,完成相关实际操作)
Implementation(用于提供动态方法的实现)
- FixedValue(方法调用返回固定值)
- MethodDelegation(方法调用委托,支持两种方式: Class的static方法调用、object的instance method方法调用)
Builder(用于创建DynamicType,相关接口以及实现后续待详解)
- MethodDefinition
- FieldDefinition
- AbstractBase
常用注解说明
注解 | 说明 |
---|---|
@Argument | 绑定单个参数 |
@AllArguments | 绑定所有参数的数组 |
@This | 当前被拦截的、动态生成的那个对象 |
@Super | 当前被拦截的、动态生成的那个对象的父类对象 |
@Origin | 可以绑定到以下类型的参数:Method 被调用的原始方法 Constructor 被调用的原始构造器 Class 当前动态创建的类 MethodHandle MethodType Field 拦截的字段 |
@DefaultCall | 调用默认方法而非super的方法 |
@SuperCall | 用于调用父类版本的方法 |
@Super | 注入父类型对象,可以是接口,从而调用它的任何方法 |
@RuntimeType | 可以用在返回值、参数上,提示ByteBuddy禁用严格的类型检查 |
@Empty | 注入参数的类型的默认值 |
@StubValue | 注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0 |
@FieldValue | 注入被拦截对象的一个字段的值 |
@Morph | 类似于@SuperCall,但是允许指定调用参数 |
生成类-原味咖啡
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("产地", "中国");
result.put("价格", "10¥");
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
//匹配make方法,返回固定值
.method(ElementMatchers.named("make")).intercept(FixedValue.value(result))
.method(ElementMatchers.named("equals")).intercept(FixedValue.value(true))
.method(ElementMatchers.named("toString")).intercept(FixedValue.value("原味咖啡"))
.defineMethod("drink", String.class, Modifier.PUBLIC).withParameter(String.class, "name")
//拦截drink方法,委托给MyDrinkInterceptor
.intercept(MethodDelegation.to(new MyDrinkInterceptor()))
//实现CoffeeNameAware接口,getName返回固定值
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FixedValue.value("原味咖啡"))
.make();
Class<?> type = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
AnimalCoffee original = (AnimalCoffee) type.newInstance();
System.out.println(original.make());
Assert.assertTrue(original.make() instanceof Map);
public class MyDrinkInterceptor {
@RuntimeType
public String drink(String name) {
return name + " 喝掉原味咖啡";
}
}
生成原味咖啡类字节码
public class AnimalCoffee$ByteBuddy$ySIOBpxK implements AnimalCoffee, CoffeeNameAware {
public boolean equals(Object var1) {
public String toString() {
return "原味咖啡";
}
//FixedValue.value 引用为静态对象的引用
public Object make() {
return value$shoqck1;
}
public String getName() {
return "原味咖啡";
}
//MethodDelegation.to 引用为静态代理对象的方法
public String drink(String name) {
return delegate$fkonef0.drink(var1);
}
}
代理类-摩卡咖啡
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
.method(ElementMatchers.isDeclaredBy(AnimalCoffee.class)
.or(ElementMatchers.isEquals())
.or(ElementMatchers.isToString()
.or(ElementMatchers.isHashCode()))
)
//委托代理
.intercept(MethodDelegation
.to(new AnimalCoffeeInterceptor(new MochaAnimalCoffee()))
)
//新增字段name
.defineField("name", String.class, Modifier.PUBLIC)
//实现CoffeeNameAware接口,返回字段name
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FieldAccessor.ofField("name"))
//新增方法,设置字段name
.defineMethod("setName", Void.TYPE, Modifier.PUBLIC)
.withParameters(String.class)
.intercept(FieldAccessor.ofBeanProperty())
//新增构造函数,设置字段name
.defineConstructor(Modifier.PUBLIC)
.withParameters(String.class)
.intercept(MethodCall.invoke(Object.class.getConstructor()).andThen(
FieldAccessor.ofField("name").setsArgumentAt(0)
))
.make();
Class<?> type3 = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
//创建有构造参数的对象
Object proxy = type3.getDeclaredConstructor(String.class).newInstance("摩卡咖啡");
System.out.println(((AnimalCoffee) proxy).make());
System.out.println(((CoffeeNameAware) proxy).getName());
public class MochaAnimalCoffee implements AnimalCoffee, CoffeeNameAware {
@Override
public Object make() {
Map<String, Object> result = new HashMap<>();
result.put("口味", "口感丝滑");
result.put("产地", "中国");
result.put("价格", "15¥");
return result;
}
@Override
public String getName() {
return null;
}
}
public class AnimalCoffeeInterceptor {
final AnimalCoffee animalCoffee;
public AnimalCoffeeInterceptor(AnimalCoffee animalCoffee) {
this.animalCoffee = animalCoffee;
}
@RuntimeType
public Object intercept(@This Object proxy, @Origin Method method, @AllArguments @RuntimeType Object[] args) throws Throwable {
String name = method.getName();
long start = System.currentTimeMillis();
if ("hashCode".equals(name)) {
return animalCoffee.hashCode();
} else if ("toString".equals(name)) {
return animalCoffee.toString();
} else if ("make".equals(name)) {
Object result = method.invoke(animalCoffee, args);
Map<String, Object> m = (Map<String, Object>) result;
m.put("热量", "360 千卡");
m.put("颜色", "深棕色");
m.put("制造耗时", System.currentTimeMillis() - start);
return m;
}
return method.invoke(animalCoffee, args);
}
}
摩卡咖啡代理类字节码
public class AnimalCoffee$ByteBuddy$ogesFMIU implements AnimalCoffee, CoffeeNameAware {
public String name;
//委托给AnimalCoffeeInterceptor
public Object make() {
return delegate$ntgsch1.intercept(this, cachedValue$v4z514W3$eafjf73, new Object[0]);
}
public String getName() {
return this.name;
}
public void setName(String var1) {
this.name = var1;
}
public AnimalCoffee$ByteBuddy$ogesFMIU(String var1) {
this.name = var1;
}
public AnimalCoffee$ByteBuddy$ogesFMIU() {
}
}
例子java-agent应用
//挂载运行应用的java-agent
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
//拦截任何的方法,并且绑定参数到MorphingCallable
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.any())
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(MorphingCallable.class))
.to(RequestInterceptor.class));
}
};
//对任何类都拦截转换
ClassFileTransformer classFileTransformer =
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(transformer)
.installOnByteBuddyAgent();
new Lottery().win();
System.out.println(new Lottery().say("ming", "hot"));
//移除
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
public interface MorphingCallable<T> {
T call(Object... arguments);
}
public class RequestInterceptor {
/**
* @SuperCall 注解注入的 Callable 参数来调用目标方法时,是无法动态修改参数的,如果想要动态修改参数,则需要用到
* @Morph 注解以及一些绑定操作,
* .intercept(MethodDelegation.withDefaultConfiguration().
* withBinders(Morph.Binder.install(MorphingCallable.class)).to(X.class)
*/
@RuntimeType
public static Object interceptor(@This Object proxy, @AllArguments Object[] allArguments, @Origin Method method,
@SuperCall Callable<?> callable, @Morph MorphingCallable<?> overrideCallable) throws Exception {
long start = System.currentTimeMillis();
System.err.println("执行对象:" + proxy);
System.err.println("执行方法:" + method);
if (Objects.nonNull(allArguments)) {
for (Object argument : allArguments) {
System.err.println("执行参数:" + argument);
if (Objects.nonNull(argument)) {
System.out.println("执行参数对象:" + argument.getClass());
}
}
}
try {
Object o = null;
if (Objects.nonNull(allArguments)) {
//可以修改参数: allArguments[1] = "cold";
o = overrideCallable.call(allArguments);
} else {
o = callable.call();
}
System.err.println("执行结果:" + o);
if (Objects.nonNull(o)) {
System.out.println("执行结果对象:" + o.getClass());
}
return o;
} finally {
System.out.println("method: " + method + ",cost: " + (System.currentTimeMillis() - start));
}
}
}
静态类拦截
使用bytebuddy的redefine机制可以对静态方法进行代理或增强,但是redefine的前提是当前类未被加载过,因此你需要在你的程序中把握增强的时机(确保在类实际加载前执行redefine方法,我们选择的加载处是在java agent的premain方法中)。同时需要通过使用bytebuddy的类型池机制代替直接获取class,可参考的代码如下:
TypePool.Resolution describe = TypePool.Default.ofSystemLoader().describe("AbstractCodec");
new ByteBuddy().redefine(describe.resolve(), ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("checkPayload"))
.intercept(MethodDelegation.to(MyInterceptor.class))
.make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
网友评论