美文网首页
java字节码增强调研笔记

java字节码增强调研笔记

作者: StonyBlue | 来源:发表于2022-04-15 16:05 被阅读0次

字节码增强,实质就是在编译期或运行期进行<font color="#D46B08">字节码插桩</font>,以便在运行期影响程序的<font color="#D46B08">执行行为</font>。按照增强时机,可以分为<font color="red">编译时增强</font>(Pluggable Annotation Processing),<font color="red">运行时增强</font>(代理类java-agent)。

屏幕快照 2022-04-19 上午11.42.08.png

实现方式:

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).png

ClassPool容器

  1. getDefault (): 返回默认的ClassPool ,单例模式,一般通过该方法创建我们的ClassPool;
  2. appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类问题;
  3. importPackage(String packageName):导入包;
  4. makeClass(String classname):创建一个空类,没有变量和方法,后序通过CtClass的函数进行添加;
  5. get(String classname)、getCtClass(String classname) : 根据类路径名获取该类的CtClass对象,用于后续的编辑。

CtClass类

  1. debugDump;String类型,如果生成。class文件,保存在这个目录下。
  2. setName(String name):给类重命名;
  3. setSuperclass(CtClass clazz):设置父类;
  4. addField(CtField f, Initializer init):添加字段(属性),初始值见CtField;
  5. addMethod(CtMethod m):添加方法(函数);
  6. toBytecode(): 返回修改后的字节码。需要注意的是一旦调用该方法,则无法继续修改CtClass
  7. toClass(): 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的CtClass
  8. writeFile(String directoryName):根据CtClass生成 .class 文件;
  9. defrost():解冻类,用于使用了toclass()、toBytecode、writeFile(),类已经被JVM加载,Javassist冻结CtClass后;
  10. detach():避免内存溢出,从ClassPool中移除一些不需要的CtClass。

Loader类加载器

  1. loadClass(String name):加载类

CtField字段

  1. CtField(CtClass type, String name, CtClass declaring) :构造函数,添加字段类型,名称,所属的类;
  2. CtField.Initializer constant():CtClass使用addField时初始值的设置;
  3. setModifiers(int mod):设置访问级别,一般使用Modifier调用常量。

CtMethod方法

  1. insertBefore(String src):在方法的起始位置插入代码;
  2. insertAfter(String src):在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
  3. insertAt(int lineNum, String src):在指定的位置插入代码;
  4. addCatch(String src, CtClass exceptionType):将方法内语句作为try的代码块,插入catch代码块src;
  5. setBody(String src):将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
  6. setModifiers(int mod):设置访问级别,一般使用Modifier调用常量;
  7. invoke(Object obj, Object... args):反射调用字节码生成类的方法。
//对于setBody $0代表this $1、$2、...代表方法的第几个参数
setBody("{$0.name = $1;}");

$符号含义

符号 含义
0,1, $2, ... this,第几个参数
$args 参数列表. $args的类型是Object[].
$$ 所有实参.例如, m($$) 等价于 m(1,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);

参考

https://xie.infoq.cn/article/d367c19896e4cef6fbb661cf7

相关文章

  • java字节码增强调研笔记

    字节码增强,实质就是在编译期或运行期进行 字节码插桩 ,以便在运行期影响程序的 执行行为 。按照增强时机,可以分为...

  • 字节码引用检测原理与实战

    一、字节码与引用检测 1.1 Java字节码 本章中的字节码重点研究Java 字节码,Java字节码(Java b...

  • 奇门遁甲之字节码与JVM指令

    最近在研究ASM 字节码增强技术,要掌握ASM 必须要先连接Java字节码结构、JVM栈帧和常用JVM指令。 本章...

  • Java 字节码增强技术 2019-02-01

    本文将介绍有哪些常见的字节码增强技术、字节码增强的实现方式、AOP实现的原理。 1. 字节码增强技术的应用场景: ...

  • Java字节码增强探秘

    作者简介泽恩,美团到店住宿业务研发团队工程师。 大家好,今天给大家推荐一篇Java字节码增强技术的文章,在实际工作...

  • Java字节码增强技术

    1.字节码 Java刚诞生的时候有一句非常著名的宣传口号:“一次编写,到处运行”。为了实现这个目的,Sun公司以及...

  • Java字节码增强探秘

    本文转载自 美团技术团队:Java字节码增强探秘[https://www.toutiao.com/article/...

  • 程序员练级攻略(2018):Java底层知识

    Java 字节码相关 首先,Java 最黑科技的玩法就是字节码编程,也就是动态修改或是动态生成 Java 字节码。...

  • Android学习笔记 手动执行Java字节码

    Android学习笔记 手动执行Java字节码 @[Android, 字节码] 背景 这一次是玩安卓知识星球的第二...

  • Java并发机制的底层原理

    Java程序执行:Java代码→Java字节码→字节码被类加载器加载到JVM里,JVM执行字节码→转化为汇编指令在...

网友评论

      本文标题:java字节码增强调研笔记

      本文链接:https://www.haomeiwen.com/subject/ozhqertx.html