bytebuddy实现原理分析 &源码分析 (二)

bytebuddy实现原理分析 &源码分析

  • 四、字节码的操作(implementation ) pkg
    • 4.1 bytecode :pkg
      • 4.1.1 StackManipulation :cls
        • 4.1.1.1 StackManipulation的子类实现
      • 4.1.2 assign :pkg
        • 4.1.2.1 Assigner : cls
          • 1 Typing: in & cls
          • 2 核心方法: in
          • 3 EqualTypesOnly : in & impl
          • 4 Refusing : in & impl
        • 4.1.2.2 primitive & reference: pkg
        • 4.1.2.2 collection :pkg
        • 4.1.2.2 Constant :pkg
        • 4.1.2.3 meber :pkg
    • 4.2 attribute :pkg
    • 4.3 Auxiliary: pkg
      • 4.3.1 AuxiliaryType : cls
        • 4.3.1.1 MethodCallProxy : cls
    • 4.4 bind : pkg
    • 4.5 Implementation :cls
      • 4.5.1 Composable : in & impl
      • 4.5.2 SpecialMethodInvocation : in & cls
      • 4.5.3 Target : in & cls
      • 4.5.4 context : in
      • 4.5.4 Compound : in & impl
    • 4.6 FixedValue :cls
  • 五、 ByteBuddy
  • 六、 AgentBuilder
    • 6.1 AgentBuilder 类结构
      • 6.1.1 嵌套的内部类
      • 6.1.2 方法
    • 6.2 listener 的机制
      • 6.2.1. AgentBuilder.Listener
      • 6.2.2. AgentBuilder.InstallationListener
  • 七、Macther
  • 八、asm 切面增强

bytebuddy实现原理分析 &源码分析(一)
bytebuddy实现原理分析 &源码分析 (二)
bytebuddy实现原理分析 &源码分析 (三)
bytebuddy实现原理分析 &源码分析 (四)

四、字节码的操作(implementation ) pkg

bytebuddy实现原理分析 &源码分析 (二)_第1张图片
byte buddy中对字节码的操作,直接调用了asm的api。
比如 FixedValue 是修改方法时定义返回的值,比如toString()=="hello"改成toString()=="changed",这个意味着要把.class字节码中的变量"hello" 替换成"changed"

如果是修改方法的定义,比如methodCall。就要修改字节码指令,本地变量栈和操作数栈。

方法的编译成字节码后就是,字节码指令和本地变量栈和操作数栈。完成方法的逻辑,就是顺序的执行字节码指令操作栈帧。不了解的同学可以先去补充字节码的知识。

asm 本身包含了字节码的各类模型,并且提供API来修改。但是这是low-level级别的。比如我要定一个加法运算,就要使用API生成多个字节码指令

byte buddy 就是对这样大量重复的操作做了上层的封装。

源码关于字节码的实现可以大概分为两部分: 字节码封装操作字节码的API

4.1 bytecode :pkg

对字节码的封装
bytebuddy实现原理分析 &源码分析 (二)_第2张图片

4.1.1 StackManipulation :cls

代表生成方法的操作数栈
bytebuddy实现原理分析 &源码分析 (二)_第3张图片
方法

方法名 描述
boolean isValid() 判断生成的操作数栈是否合法
Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext); methodVisitor是asm的接口用来生成字节码,Implementation.Context中定义了字节码。这个函数的作用是应用定义的字节码

内部类
bytebuddy实现原理分析 &源码分析 (二)_第4张图片

方法名 类型 描述
Illegal 实现 非法的实现
Trivial 实现 合法的实现,但是不会对原先的字节码有任何更改
Compound 实现 可以对多个方法的栈大小进行合并运算

4.1.1.1 StackManipulation的子类实现

类名 类型 描述
Addition 实现 操作数栈的数字想加
Duplication 实现 复制栈定的数
Multiplication 实现 操作数栈的数字相乘
Removal 实现 移出操作数栈的值
TypeCreation 实现 当一个构造器方法被调用时,创建一个未定义的类型
Throw 实现 必须位于栈顶,当StackManipulation被调用时抛出一个异常java.lang.Throwable

看一个具体的例子Duplication, 其他的方法也类似: 封装字节码指令;重写apply
示例
复制一个在操作数栈,复制一个double 类型的数。和普通int不一样,long和double需要占1到2卡槽(2* 4 byte)。 所以这个枚举变量DOUBLE,和字节码指令int DUP2 = 92;关联。
封装字节码指令

  /**
     * A duplication of a double-sized stack value.
     */
    DOUBLE(StackSize.DOUBLE, Opcodes.DUP2) {
        @Override
        public StackManipulation flipOver(TypeDefinition typeDefinition) {
            switch (typeDefinition.getStackSize()) {
                case SINGLE:
                    return WithFlip.DOUBLE_SINGLE;
                case DOUBLE:
                    return WithFlip.DOUBLE_DOUBLE;
                default:
                    throw new IllegalArgumentException("Cannot flip: " + typeDefinition);
            }
        }
    };

新建一个Duplication,有两个方法

	/** 直接输入栈大小,和字节码指令
	*/
    Duplication(StackSize stackSize, int opcode) {
        size = stackSize.toIncreasingSize();
        this.opcode = opcode;
    }
    /**
    *  根据类型的定义,获取栈大小
    */
    public static Duplication of(TypeDefinition typeDefinition) {
        switch (typeDefinition.getStackSize()) {
            case SINGLE:
                return SINGLE;
            case DOUBLE:
                return DOUBLE;
            case ZERO:
                return ZERO;
            default:
                throw new AssertionError("Unexpected type: " + typeDefinition);
        }
    }

实现apply
看看apply的实现,核心是methodVisitor.visitInsn(opcode);这就是asm原生的方法,作用实在字节码码中生成这么一个指令。

    public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
        methodVisitor.visitInsn(opcode);
        return size;
    }

4.1.2 assign :pkg

bytebuddy实现原理分析 &源码分析 (二)_第5张图片
这个包封装了jvm规范中的两个基本类型:基本类型,引用类型。负责控制不同类型之间的转换

4.1.2.1 Assigner : cls

一个Assigner负责把一个类型A转换成类型B。比如一个Assigner可以负责类型转化:装箱,把一个基本类型(int),转化为包转类(Integer);拆箱,正好相反。
bytebuddy实现原理分析 &源码分析 (二)_第6张图片

1 Typing: in & cls

指示,累型的转化是静态的还是动态的。静态的意思是,调用前已经确定了。动态类型时,生成是才确定。默认值是动态生成。

 		/**
         * 静态生成
         */
        STATIC(false),

        /**
         * 动态生成
         */
        DYNAMIC(true);
2 核心方法: in
  • StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing);
3 EqualTypesOnly : in & impl

Assigner的实现。

 enum EqualTypesOnly implements Assigner {

        /**
         * An type assigner that only considers equal generic types to be assignable.
         */
        GENERIC {
            /** {@inheritDoc} */
            public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
                return source.equals(target)
                        ? StackManipulation.Trivial.INSTANCE
                        : StackManipulation.Illegal.INSTANCE;
            }
        },

        /**
         * A type assigner that considers two generic types to be equal if their erasure is equal.
         */
        ERASURE {
            /** {@inheritDoc} */
            public StackManipulation assign(TypeDescription.Generic source, TypeDescription.Generic target, Typing typing) {
                return source.asErasure().equals(target.asErasure())
                        ? StackManipulation.Trivial.INSTANCE
                        : StackManipulation.Illegal.INSTANCE;
            }
        };
    }

GENERIC 转换前直接判断类型是否想等
ERASURE擦除类型后判断

4 Refusing : in & impl

代表了不能转化的类型。调用assign直接抛出异常。

4.1.2.2 primitive & reference: pkg

bytebuddy实现原理分析 &源码分析 (二)_第7张图片
封装了 基本类型和引用类型各自的转化方法。可以进入源码细看,不什么

4.1.2.2 collection :pkg

在这里插入图片描述
封装了数组与集合的字节码。
这个一个示例,可以看见它的定义把不同类型和对应的字节码指令进行了绑定
比如BYTE(Opcodes.BALOAD, Opcodes.BASTORE, StackSize.SINGLE)Opcodes.BALOAD代表byte值的加载;Opcodes.BASTORE代表了byte值放入操作数栈的指令;StackSize.SINGLE代表了大小,single是4byte

public enum ArrayAccess {

    /**
     * Access for a {@code byte}- or {@code boolean}-typed array.
     */
    BYTE(Opcodes.BALOAD, Opcodes.BASTORE, StackSize.SINGLE),

    /**
     * Access for a {@code short}-typed array.
     */
    SHORT(Opcodes.SALOAD, Opcodes.SASTORE, StackSize.SINGLE),

    /**
     * Access for a {@code char}-typed array.
     */
    CHARACTER(Opcodes.CALOAD, Opcodes.CASTORE, StackSize.SINGLE),

    /**
     * Access for a {@code int}-typed array.
     */
    INTEGER(Opcodes.IALOAD, Opcodes.IASTORE, StackSize.SINGLE),

    /**
     * Access for a {@code long}-typed array.
     */
    LONG(Opcodes.LALOAD, Opcodes.LASTORE, StackSize.DOUBLE),

    /**
     * Access for a {@code float}-typed array.
     */
    FLOAT(Opcodes.FALOAD, Opcodes.FASTORE, StackSize.SINGLE),

    /**
     * Access for a {@code double}-typed array.
     */
    DOUBLE(Opcodes.DALOAD, Opcodes.DASTORE, StackSize.DOUBLE),

    /**
     * Access for a reference-typed array.
     */
    REFERENCE(Opcodes.AALOAD, Opcodes.AASTORE, StackSize.SINGLE);

  

4.1.2.2 Constant :pkg

同collection类型,是封装了常量池类型
bytebuddy实现原理分析 &源码分析 (二)_第8张图片

4.1.2.3 meber :pkg

类似,但是更复杂一点。代表了field和method。
bytebuddy实现原理分析 &源码分析 (二)_第9张图片
注意Opcodes.INVOKEVIRTUALOpcodes.INVOKESTATIC是几种字节码指令,用来在方法中调用其他方法的的指针。

4.2 attribute :pkg

bytebuddy实现原理分析 &源码分析 (二)_第10张图片
代表了字节码的属性。
比如method的字节码结构,它的属性就是一个表。

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index; 
    // 这里
    u2             attributes_count;
    attribute_info attributes[attributes_count];
    // 结束
}

分析其中一个类MethodAttributeAppender
核心的接口

  • void apply(MethodVisitor methodVisitor, MethodDescription methodDescription, AnnotationValueFilter annotationValueFilter);

methodVisitor就是asm 用来生成类的接口;methodDescription方法的定义;annotationValueFilter就是注解的过滤器,可以过滤掉methodDescription不想要的注解。

4.3 Auxiliary: pkg

辅助类型,什么辅助类型:修改原始类的字节码,织入一些方法为其他的织入方法提供帮助,注意一定要实现有意义的equals()hashCode(),避免相同的目标类重复放入。

举个例
class A的方法String hello(),要替换成class B的方法String hello()

简单的实现是: 修改A的实现字节码,实现中调用B的hello方法。
A.hello()–>B.hello()

复杂的实现:定义一个helloProxy()方法,修改A的实现字节码,实现中调的helloProxy()方法。然后helloProxy()中调用B的实现。
A.hello–> Proxy.helloProxy()–>B.helloProxy()。

复杂实现的好处是,如果要换成class C的方法String hello(),那么无需再特定的实现,和Proxy.helloProxy()交互就可以。

所以看出织入A的代码并不是目的代码,而是一个辅助的工具类Proxy。这就是辅助类的作用。

bytebuddy实现原理分析 &源码分析 (二)_第11张图片

4.3.1 AuxiliaryType : cls

bytebuddy实现原理分析 &源码分析 (二)_第12张图片
核心的方法

  • DynamicType make(String auxiliaryTypeName, ClassFileVersion classFileVersion, MethodAccessorFactory methodAccessorFactory);
    生成
  • SignatureRelevant注解,标注辅助类型是否参与类型签名的生成,避免生成前面时报NoClassDefFoundError错误

4.3.1.1 MethodCallProxy : cls

AuxiliaryType的实现,就是前面描述的方法代理
make方法的实现:生成了一个代理类

   public DynamicType make(String auxiliaryTypeName,
                            ClassFileVersion classFileVersion,
                            MethodAccessorFactory methodAccessorFactory) {
        MethodDescription accessorMethod = methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT);
        LinkedHashMap<String, TypeDescription> parameterFields = extractFields(accessorMethod);
        DynamicType.Builder<?> builder = new ByteBuddy(classFileVersion)
                .with(TypeValidation.DISABLED)
                .with(PrecomputedMethodGraph.INSTANCE)
                .subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
                .name(auxiliaryTypeName)
                .modifiers(DEFAULT_TYPE_MODIFIER)
                .implement(Runnable.class, Callable.class).intercept(new MethodCall(accessorMethod, assigner))
                .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0])
                .defineConstructor().withParameters(parameterFields.values())
                .intercept(ConstructorCall.INSTANCE);
        for (Map.Entry<String, TypeDescription> field : parameterFields.entrySet()) {
            builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE);
        }
        return builder.make();
    }

其余的类不在详细的说了。

4.4 bind : pkg

4.5 Implementation :cls

bytebuddy实现原理分析 &源码分析 (二)_第13张图片
Implementation 负责把动态创建的类Dynamic.type,转化为字节码。一个Implementation的使用可以按如下两步。

  1. Implementation能够去准备一个instrumentedType,通过添加field和辅助类(以便于添加方法的的 Implementation。此外,LoadedTypeInitializer和 类型初始化的字节码,可以被注册为instrumentedType
  2. Implementation提供一个code appender,可以被用来做方法的实现的委托调用。code appender也负责去提供在 step 1中添加的类型。

同时任何的Implementation应该提供有意义的equalshashCode,避免重复生成。
bytebuddy实现原理分析 &源码分析 (二)_第14张图片
核心的方法

  • ByteCodeAppender appender(Target implementationTarget);

Target是当前的implementation,这个的意思是基于当前的implementation创建一个ByteCodeAppender

4.5.1 Composable : in & impl

链式的implementation,

   interface Composable extends Implementation {

        /**
         * Appends the supplied implementation to this implementation.
         *
         * @param implementation The subsequent implementation.
         * @return An implementation that combines this implementation with the provided one.
         */
        Implementation andThen(Implementation implementation);

        /**
         * Appends the supplied composable implementation to this implementation.
         *
         * @param implementation The subsequent composable implementation.
         * @return A composable implementation that combines this implementation with the provided one.
         */
        Composable andThen(Composable implementation);
    }

4.5.2 SpecialMethodInvocation : in & cls

特殊的方法调用。比如class Aclass B extends A。现在只有在B内才能调用super方法。但是可以通过修改字节码的方式,委托调用super方法。这就是特殊的方式调用,但不限于上面描述的场景。
bytebuddy实现原理分析 &源码分析 (二)_第15张图片

interface SpecialMethodInvocation extends StackManipulation {

4.5.3 Target : in & cls

bytebuddy实现原理分析 &源码分析 (二)_第16张图片一个 implementation 的目标。目标必须是不可变得,并且查询时返回固定结果,不能有随机的结果。Target提供生成implementation的一切信息。

4.5.4 context : in

十分重要的一个内部类。包含了一个 Implenentation有关的上下文信息
bytebuddy实现原理分析 &源码分析 (二)_第17张图片
核心方法

  • register 注册上下文信息
    这个是子类的一个实现,不难看出auxiliaryTypes保存了所有辅助类的信息
 public TypeDescription register(AuxiliaryType auxiliaryType) {
                DynamicType dynamicType = auxiliaryTypes.get(auxiliaryType);
                if (dynamicType == null) {
                    dynamicType = auxiliaryType.make(auxiliaryTypeNamingStrategy.name(instrumentedType), auxiliaryClassFileVersion, this);
                    auxiliaryTypes.put(auxiliaryType, dynamicType);
                }
                return dynamicType.getTypeDescription();
            }
  • FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType);
    缓存 field的定义
  • 默认实现类的drain方法
    bytebuddy实现原理分析 &源码分析 (二)_第18张图片
public void drain(TypeInitializer.Drain drain,
                  ClassVisitor classVisitor,
                  AnnotationValueFilter.Factory annotationValueFilterFactory) {
    fieldCacheCanAppendEntries = false;
    TypeInitializer typeInitializer = this.typeInitializer;
    for (Map.Entry<FieldCacheEntry, FieldDescription.InDefinedShape> entry : registeredFieldCacheEntries.entrySet()) {
        FieldVisitor fieldVisitor = classVisitor.visitField(entry.getValue().getModifiers(),
                entry.getValue().getInternalName(),
                entry.getValue().getDescriptor(),
                entry.getValue().getGenericSignature(),
                FieldDescription.NO_DEFAULT_VALUE);
        if (fieldVisitor != null) {
            fieldVisitor.visitEnd();
            typeInitializer = typeInitializer.expandWith(entry.getKey().storeIn(entry.getValue()));
        }
    }
    drain.apply(classVisitor, typeInitializer, this);
    for (TypeWriter.MethodPool.Record record : registeredAccessorMethods.values()) {
        record.apply(classVisitor, this, annotationValueFilterFactory);
    }
    for (TypeWriter.MethodPool.Record record : registeredGetters.values()) {
        record.apply(classVisitor, this, annotationValueFilterFactory);
    }
    for (TypeWriter.MethodPool.Record record : registeredSetters.values()) {
        record.apply(classVisitor, this, annotationValueFilterFactory);
    }
}

这个类就是使用asm的接口classVisitor.visitField,生成类文件。

4.5.4 Compound : in & impl

一组implemention

4.6 FixedValue :cls

Implementation的一个实现,用来修改原始方法,让其返回一个我们定义的值。
bytebuddy实现原理分析 &源码分析 (二)_第19张图片
FixedValue有两个field

  • assignertyping。之前介绍过assigner是负责类型转换的,typing是决定是否强制装换的。

核心的方法

  • ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
    Context implementationContext,
    MethodDescription instrumentedMethod,
    TypeDescription.Generic fixedValueType,
    StackManipulation valueLoadingInstruction)

下面是类的实现调用visit方法,生成成员。最后返回的StackSize,相当于VisitEnd的作用

    protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor,
                                          Context implementationContext,
                                          MethodDescription instrumentedMethod,
                                          TypeDescription.Generic fixedValueType,
                                          StackManipulation valueLoadingInstruction) {
        StackManipulation assignment = assigner.assign(fixedValueType, instrumentedMethod.getReturnType(), typing);
        if (!assignment.isValid()) {
            throw new IllegalArgumentException("Cannot return value of type " + fixedValueType + " for " + instrumentedMethod);
        }
        StackManipulation.Size stackSize = new StackManipulation.Compound(
                valueLoadingInstruction,
                assignment,
                MethodReturn.of(instrumentedMethod.getReturnType())
        ).apply(methodVisitor, implementationContext);
        return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
    }

五、 ByteBuddy

这个是最外层的类,使用者,直接用这个类来生成想要的类。官方文档已经有了充分的介绍。这里罗列下方法。看着方法就可以猜到用法。
bytebuddy实现原理分析 &源码分析 (二)_第20张图片
bytebuddy实现原理分析 &源码分析 (二)_第21张图片
bytebuddy实现原理分析 &源码分析 (二)_第22张图片

六、 AgentBuilder

用来生成java agent的类。
当一个应用变得庞大和模块化时,使用java agent机制很合适。

定义transformer,每有一个类加载时,触发transformer逻辑,对类进行匹配和修改。

bytebuddy提供了Agentbuilder,封装了一系列API来新建一个 agent。
示例
假设我们定义了一个注解ToString,我们匹配所有标注@ToString的类,修改toString方法,让其返回"transformed"

class ToStringAgent {
  public static void premain(String arguments, Instrumentation instrumentation) {
    new AgentBuilder.Default()
        .type(isAnnotatedWith(ToString.class))
        .transform(new AgentBuilder.Transformer() {
      @Override
      public DynamicType.Builder transform(DynamicType.Builder builder,
                                              TypeDescription typeDescription,
                                              ClassLoader classloader) {
        return builder.method(named("toString"))
                      .intercept(FixedValue.value("transformed"));
      }
    }).installOn(instrumentation);
  }
}
  • type 接受 一个ElementMatcher 匹配 ToString注解
  • transform 接受AgentBuilder.Transformer 来描述修改逻辑
// 这里匹配类里面的`toString`方法,使用`intercept`修改返回值
 public DynamicType.Builder transform(DynamicType.Builder builder,
                                              TypeDescription typeDescription,
                                              ClassLoader classloader) {
        return builder.method(named("toString"))
                      .intercept(FixedValue.value("transformed"));
      }
  • installOn ,将修改,应用到instrumentation中。

6.1 AgentBuilder 类结构

bytebuddy的API

6.1.1 嵌套的内部类

order Modifier and Type Interface Description
0 static interface AgentBuilder.CircularityLock 一个循环锁,阻止ClassFileLocator被提前使用。发生在,一个 transformation逻辑可能会导致另外的类被加载。如果不避免这样的循环依赖,就会抛出ClassCircularityError错误,导致类加载失败
1 static interface AgentBuilder.ClassFileBufferStrategy 关于class定义的字节缓存buffer如何被使用的策略
2 static class AgentBuilder.Default AgentBuilder的默认实现。默认情况下,byte buddy 无视任何被bootstrap loader加载的类和合成类。1. Self-injection 和 rebase 模式开启 2. 为了避免class的格式发生改变,AgentBuilder.disableClassFormatChanges() 3. 所有的类型解析都是 PoolStrategy.Default#FAST,忽略所有的debug信息
3 static interface AgentBuilder.DescriptionStrategy 策略,描述当转化和定义一个类时,如何解决TypeDescription定义,这个解决指的是寻找+处理
4 static interface AgentBuilder.FallbackStrategy 失败策略,允许万一失败时,可以再次进行transformation或者redefine/retransformation。如果这样做了,可能就是会使用typepool,而不是一个已经加载的type description–相当于某个class的备份。如果class loader不能找到所有的依赖的类,会抛出异常。使用typepool,由于时懒加载,所以会规避异常,直到被使用
5 static interface AgentBuilder.Identified 用来描述AgentBuilder处理哪几种mathcer,这个就是提供给mather一个标识,便于筛选
6 static interface AgentBuilder.Ignored 允许声明,忽略那些具体的方法
7 static interface AgentBuilder.InitializationStrategy 初始化策略,决定LoadedTypeInitializer是如何加载辅助类。agentbuilder不能重用TypeResolutionStrategy策略,是因为Javaagents不能获取到一个被transformation过,已经加载的类。所以所以不同的初始化加载策略更有效
8 static interface AgentBuilder.InjectionStrategy 将辅助类注入classloader的策略
9 static interface AgentBuilder.InstallationListener 监听器,安装和重置class file transformer事件会唤醒这个监听器
10 static class AgentBuilder.LambdaInstrumentationStrategy lambda风格的语法特性开启
11 static interface AgentBuilder.Listener 一个an instrumentation 运行时会触发一堆事件,这个监听器是用来接受这些事件的
12 static interface AgentBuilder.LocationStrategy 修改一个类时,描述如何去创建ClassFileLocator的策略
13 static interface AgentBuilder.Matchable 继承了mathcer的一个抽象实现,链式的结构
14 static interface AgentBuilder.PoolStrategy 描述AgentBuilder如何寻找和加载类TypeDescription的策略
15 static interface AgentBuilder.RawMatcher 一个matcher,用来匹配类并决定AgentBuilder.TransformerClassFileTransformer运行期间是否被使用
16 static interface AgentBuilder.RedefinitionListenable 允许在redefine的过程中注册一堆监听器
17 static class AgentBuilder.RedefinitionStrategy redefinition策略,描述agent如何控制已经被agent 加载到内存里面的类
18 static interface AgentBuilder.Transformer 应用DynamicType(定义的类修改),将DynamicType,对接到ClassFileTransformer
19 static interface AgentBuilder.TransformerDecorator AgentBuilder.Transformer的装饰器
20 static interface AgentBuilder.TypeStrategy 描述创建一个要被修改的类型,如何创建的方式

6.1.2 方法

相当于API的翻译,会有简单解释,觉得不准请看上面的API文档。

  • 加载过 往往意味值loaded,就是指classloader已经加载过目标类。bytebuddy通常就是感知类的加载,并且返回一个加工过的类。如此完成字节码修改的作用。
order return type method 描述
0 AgentBuilder with(ByteBuddy byteBuddy) ByteBuddy是用来创建DynamicType的。这里就是接受并生成一个AgentBuilder
1 AgentBuilder with(Listener listener) 注册监听器,创建agent的时候,会唤醒这个Listener。注意的是可以注册多个,如果早已经注册,也会重复唤醒,不会只唤醒一个
2 AgentBuilder with(CircularityLock circularityLock) 一个循环锁,被用于被执行的代码会加载新的类时,会获取这个锁。当锁被获取时,任何classfiletransformer都不能transforming 任何类,默认,所有被创建的agent都使用一个CircularityLock,避免造成一个ClassCircularityError。就是避免被执行的代码加载新类,同时其他agent也在转化这个类,造成一个循环依赖的一个锁
3 AgentBuilder with(PoolStrategy poolStrategy) 加载的类时的策略
4 AgentBuilder with(LocationStrategy locationStrategy) 寻找类的位置,利用 class name 加载类二进制数据的策略
5 AgentBuilder with(TypeStrategy typeStrategy) type 应该如何被transformed,eg, rebased或者redefined
6 AgentBuilder with(InitializationStrategy initializationStrategy) 生成一个类时的初始化策略,一个初始化策略是在类被加载后,启动时生效。初始化行为,必须在transformation行为之后,因为java agent 是在加载一个类之前生效。默认,被注入到对象的初始化逻辑,需要查询一个全局对象来找到所有需要被注入到目标类型的对象
7 RedefinitionListenable.WithoutBatchStrategy with(RedefinitionStrategy redefinitionStrategy) 明确早已经被修改过且加被加载过类的优先级,目的是来安转transformer会用到。注意定一个redefinition strategy会重置之前的一些列的策略。重要的是,绝大多数JVM不支持被加载过的类,类结构被重新修改,因此默认打开AgentBuilder#disableClassFormatChanges()
8 AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) Lambda表达式特性,忽虑掉
9 AgentBuilder with(DescriptionStrategy descriptionStrategy)) 定义被加载类的,解决(resolving)策略。
10 AgentBuilder (FallbackStrategy fallbackStrategy) 失败策略,比如转换失败时,允许重试一次
11 AgentBuilder with(ClassFileBufferStrategy classFileBufferStrategy) 类定义的buffer如何被使用
12 AgentBuilder with(InstallationListener installationListener) agent安转时,会有安装事件,这个时间会唤醒这个监听器。这个监听器,只有在classfiletransformer安装时会被触发。classfiletransformer的安装就是agent builder通过创建的ResettableClassFileTransformer执行installation methods 和uninstalled
13 AgentBuilder with(InjectionStrategy injectionStrategy) 辅助类注入到classloader的策略
14 AgentBuilder enableNativeMethodPrefix(String prefix) 给被修改的方法,添加本地方法前缀。注意这个前缀也能给非本地方法用。当Instrumentation安装agent是,也能用这个前缀来注册
15 AgentBuilder disableNativeMethodPrefix() 关闭一个本地方法的前缀
16 AgentBuilder disableClassFormatChanges() 禁止所有对class file 的改变,当时用这个策略时,就不存在使用rebase的可能—被修改的方法会完全被替代,而不是被重名。可去搜索rebase和redefine的区别。还有就是在生成类型是,加载初始化动作。这个行为和设置InitializationStrategy.NoOpTypeStrategy.Default#REDEFINE_FROZEN是等价的。也等同于配置ByteBuddyImplementation.Context.Disabled。使用这个策略是让bute buddy 穿件一个 冻结的 instrumented类型和排除调所有的配置行为
17 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class… type) module ,jdk9的概念,不用管
18 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule… module)
19 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection modules)
20 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class… type)
21 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule… module)
22 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection modules) 都是moudle权限控制相关的
23 Identified.Narrowabler type(ElementMatcher typeMatcher) 每一个类被加载时会触发一个事件,被agent感知到。这个方法就是用来配一个被加载的类型,目的是应用AgentBuilder.Transformer的修改,在这个类型被加载之前。如果有几个matcher都命中一个类型,那么最后一个生效。如果这个matcher是链式的,即还有下一个matcher,那么matcher执行的顺序就是注册的顺序,后面matcher对应的transformations会覆盖前面的。如果不想被覆盖可以注册为terminal最后一个Identified.Extendable#asTerminalTransformation(),这样就不有有下一个matcher被使用。注意: AgentBuilder#ignore(ElementMatcher)会在mather之前被应用,来排除忽略的类。
24 Identified.Narrowabler type(ElementMatcher typeMatcher, ElementMatcher classLoaderMatcher) 唯一的不同是添加了classLoaderMatcher参数,这个classLoader被用来加载目标类的loader
25 Identified.Narrowabler Identified.Narrowable type(ElementMatcher typeMatcher,ElementMatcher classLoaderMatcher,ElementMatcher moduleMatcher); moduleMatcher jdk9的语法,只是多了层判断
26 Identified.Narrowabler type(RawMatcher matcher); RawMatcher 后面详细分析,文档只说用来决定Transformer是否被使用
27 Ignored ignore(ElementMatcher typeMatcher); 无视方法的matcher
28 Ignored ignore(ElementMatcher typeMatcher, ElementMatcher classLoaderMatcher)
29 Ignored ignore(RawMatcher rawMatcher)
30 ClassFileTransformer makeRaw() ClassFileTransformer是jdk instrument包的类。这个方法是创建一个原生的这样的类。当时用原生的ClassFileTransformerInstallationListener回调就不会生效,而且RedefinitionStrategy的策略也不会应用到当前加载的类中
31 ResettableClassFileTransformer installOn(Instrumentation instrumentation) 单纯使用instrument时,往往是在premain或者agentmain函数里,执行instrumentation.addTransformer(ClassFileTransformer),installOn 就是将创建一个ResettableClassFileTransformer添加进instrumentation。如果retransformation是打开的,那么retransformed时让ClassFileTransformer再次生效。意思是ClassFileTransformer是链式的,每个ClassFileTransformer被添加的时候有个canRetransformed属性。retransformed,就是修改已经加载内存里面的类,执行时会再次触发ClassFileTransformer,这时只会触发canRetransformed为true的ClassFileTransformer
32 ResettableClassFileTransformer ResettableClassFileTransformer installOn(Instrumentation instrumentation, TransformerDecorator transformerDecorator); installOn时接受额外的Transformer
33 ResettableClassFileTransformer installOnByteBuddyAgent() ByteBuddyAgent是bytebutty对jdk.attachagentmain的一个封装。根据目标jvm实例的pid,在运行中attach上去,发出加载agent.jar包命令
34 ResettableClassFileTransformer installOnByteBuddyAgent(TransformerDecorator transformerDecorator);

6.2 listener 的机制

不同于Bytebuddy类,这个类还有 listener机制,为什么呢。
java的instrument包加载javaagent包,其实是有时间点的,可以人为的在不同时刻使用监听器机制做些什么。

6.2.1. AgentBuilder.Listener

这个监听器发生在 instrumentation api作用的时候,也就是开始加载java agent的时候。
bytebuddy实现原理分析 &源码分析 (二)_第23张图片

  • void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);
    一个类型新被transformer开始加载前。
  • void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType);
    被成功转换时
  • 一个类型被忽略的时候
    void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded);
  • 一个类型加载时报错
    void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable);
  • 一个类型被成功加载完成
    void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);

这个方法在哪里被调用了,比如bytebuddy实现原理分析 &源码分析 (二)_第24张图片
transform这个方式就是继承ClassFileTransformer类。ClassFileTransformer是instrument包中类,当进行类的修改时会执行这个transform()方法。
可以看到这个类内,一开始就调用了onDiscovery
其他的方法也类似。

6.2.2. AgentBuilder.InstallationListener

这个是针对bytebuddy接口的调用。
bytebuddy实现原理分析 &源码分析 (二)_第25张图片

  • reset的含义。下线transformer,然后使用redined 所以转化换的类,等于没有这个transformer时候的转化后的结构

这个会在那哪里调用呢,比如 agent的installOn,最终会调到一个doInstall的方法。
bytebuddy实现原理分析 &源码分析 (二)_第26张图片

七、Macther

bytebuddy实现原理分析 &源码分析 (二)_第27张图片
bytebuddy实现原理分析 &源码分析 (二)_第28张图片

  • named返回一个macther的接口
  • instrumentedType.getDeclaredFields().filter(named(ENUM_VALUES)).getOnly()
    getDeclaredFields找到一个复合match条件的方法

八、asm 切面增强

你可能感兴趣的:(bytebuddy实现原理分析 &源码分析 (二))