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
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
对字节码的封装
4.1.1 StackManipulation :cls
代表生成方法的操作数栈
方法
方法名 | 描述 | |
---|---|---|
boolean isValid()
|
判断生成的操作数栈是否合法 | |
Size apply (MethodVisitor methodVisitor, Implementation.Context implementationContext);
|
methodVisitor 是asm的接口用来生成字节码,Implementation.Context 中定义了字节码。这个函数的作用是应用定义的字节码
|
内部类
方法名 | 类型 | 描述 |
---|---|---|
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) {@Overridepublic 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
这个包封装了jvm规范中的两个基本类型:基本类型,引用类型
。负责控制不同类型之间的转换
4.1.2.1 Assigner : cls
一个Assigner负责把一个类型A
转换成类型B
。比如一个Assigner
可以负责类型转化:装箱,把一个基本类型(int),转化为包转类(Integer);拆箱,正好相反。
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
封装了 基本类型和引用类型各自的转化方法。可以进入源码细看,不什么
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类型,是封装了常量池类型
4.1.2.3 meber :pkg
类似,但是更复杂一点。代表了field和method。
注意Opcodes.INVOKEVIRTUAL
,Opcodes.INVOKESTATIC
是几种字节码指令,用来在方法中调用其他方法的的指针。
4.2 attribute :pkg
代表了字节码的属性。
比如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
。这就是辅助类的作用。
4.3.1 AuxiliaryType : cls
核心的方法
- 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
Implementation 负责把动态创建的类Dynamic.type
,转化为字节码。一个Implementation
的使用可以按如下两步。
Implementation
能够去准备一个instrumentedType
,通过添加field
和辅助类(以便于添加方法的的Implementation
。此外,LoadedTypeInitializer
和 类型初始化的字节码,可以被注册为instrumentedType
。Implementation
提供一个code appender
,可以被用来做方法的实现的委托调用。code appender
也负责去提供在 step 1中添加的类型。
同时任何的Implementation
应该提供有意义的equals
和hashCode
,避免重复生成。
核心的方法
- 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 A
和 class B extends A
。现在只有在B内才能调用super
方法。但是可以通过修改字节码的方式,委托调用super
方法。这就是特殊的方式调用,但不限于上面描述的场景。
interface SpecialMethodInvocation extends StackManipulation {
4.5.3 Target : in & cls
一个 implementation 的目标。目标必须是不可变得,并且查询时返回固定结果,不能有随机的结果。Target提供生成implementation
的一切信息。
4.5.4 context : in
十分重要的一个内部类。包含了一个 Implenentation有关的上下文信息
核心方法
- 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
方法
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的一个实现,用来修改原始方法,让其返回一个我们定义的值。
FixedValue有两个field
assigner
和typing
。之前介绍过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
这个是最外层的类,使用者,直接用这个类来生成想要的类。官方文档已经有了充分的介绍。这里罗列下方法。看着方法就可以猜到用法。
六、 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() {@Overridepublic 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<T extends AgentBuilder.Matchable> | 继承了mathcer的一个抽象实现,链式的结构 |
14 | static interface | AgentBuilder.PoolStrategy |
描述AgentBuilder 如何寻找和加载类TypeDescription 的策略
|
15 | static interface | AgentBuilder.RawMatcher |
一个matcher ,用来匹配类并决定AgentBuilder.Transformer 在ClassFileTransformer 运行期间是否被使用
|
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.NoOp 和TypeStrategy.Default#REDEFINE_FROZEN 是等价的。也等同于配置ByteBuddy 为Implementation.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<? extends JavaModule> modules) | |
20 | AgentBuilder | assureReadEdgeFromAndTo(Instrumentation instrumentation, Class<?>… type) | |
21 | AgentBuilder | assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule… module) | |
22 | AgentBuilder | assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) | 都是moudle权限控制相关的 |
23 | Identified.Narrowabler | type(ElementMatcher<? super TypeDescription> typeMatcher) |
每一个类被加载时会触发一个事件,被agent感知到。这个方法就是用来配一个被加载的类型,目的是应用AgentBuilder.Transformer 的修改,在这个类型被加载之前。如果有几个matcher都命中一个类型,那么最后一个生效。如果这个matcher是链式的,即还有下一个matcher,那么matcher 执行的顺序就是注册的顺序,后面matcher对应的transformations 会覆盖前面的。如果不想被覆盖可以注册为terminal 最后一个Identified.Extendable#asTerminalTransformation() ,这样就不有有下一个matcher被使用。注意: AgentBuilder#ignore(ElementMatcher) 会在mather之前被应用,来排除忽略的类。
|
24 | Identified.Narrowabler | type(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) |
唯一的不同是添加了classLoaderMatcher 参数,这个classLoader 被用来加载目标类的loader
|
25 | Identified.Narrowabler | Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher,ElementMatcher<? super ClassLoader> classLoaderMatcher,ElementMatcher<? super JavaModule> moduleMatcher); | moduleMatcher jdk9的语法,只是多了层判断 |
26 | Identified.Narrowabler | type(RawMatcher matcher); |
RawMatcher 后面详细分析,文档只说用来决定Transformer 是否被使用
|
27 | Ignored | ignore(ElementMatcher<? super TypeDescription> typeMatcher); | 无视方法的matcher |
28 | Ignored | ignore(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) | |
29 | Ignored | ignore(RawMatcher rawMatcher) | |
30 | ClassFileTransformer | makeRaw() |
ClassFileTransformer 是jdk instrument包的类。这个方法是创建一个原生的这样的类。当时用原生的ClassFileTransformer ,InstallationListener 回调就不会生效,而且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.attach 和agentmain 的一个封装。根据目标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
的时候。
- void
onDiscovery
(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);
一个类型新被transformer
开始加载前。 - void
onTransformation
(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType);
被成功转换时 - 一个类型被忽略的时候
voidonIgnored
(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded); - 一个类型加载时报错
voidonError
(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable); - 一个类型被成功加载完成
voidonComplete
(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded);
这个方法在哪里被调用了,比如
transform这个方式就是继承ClassFileTransformer
类。ClassFileTransformer
是instrument包中类,当进行类的修改时会执行这个transform()
方法。
可以看到这个类内,一开始就调用了onDiscovery
。
其他的方法也类似。
6.2.2. AgentBuilder.InstallationListener
这个是针对bytebuddy接口的调用。
- reset的含义。下线transformer,然后使用redined 所以转化换的类,等于没有这个transformer时候的转化后的结构
这个会在那哪里调用呢,比如 agent的installOn
,最终会调到一个doInstall
的方法。
七、Macther
- named返回一个macther的接口
instrumentedType.getDeclaredFields().filter(named(ENUM_VALUES)).getOnly()
从getDeclaredFields
找到一个复合match条件的方法
八、asm 切面增强
bytebuddy实现原理分析 源码分析 (二)相关推荐
- bytebuddy实现原理分析 源码分析 (三)- advice 详解
advice详解 八.advice 8.1 AsmVisitorWrapper 8.1.1 ForDeclareFields 8.1.1.1 Entry 8.1.1.2 DispatchingVisi ...
- LIRE原理与源码分析(二)——相关接口
1. LIRE原理与源码分析(二)-- 代码结构 2. LIRE原理与源码分析(二)-- 相关接口 上一篇文章介绍了LIRE的基本内容和源码的代码结构.本文针对LIRE中主要的三个接口(LireFea ...
- 深入理解Spark 2.1 Core (十二):TimSort 的原理与源码分析
在博文<深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析 >中我们提到了: 使用Sort等对数据进行排序,其中用到了TimSort 这篇博文我们就来 ...
- SIFT原理与源码分析:DoG尺度空间构造
<SIFT原理与源码分析>系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548 尺度空间理论 自然界中的物体随着观 ...
- 深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析
在上一篇<深入理解Spark 2.1 Core (九):迭代计算和Shuffle的原理与源码分析>提到经过迭代计算后, SortShuffleWriter.write中: // 根据排序方 ...
- ConcurrentHashMap实现原理及源码分析
ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...
- concurrenthashmap_ConcurrentHashMap实现原理及源码分析
ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...
- 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源码分析
我们曾经在<深入理解Spark 2.1 Core (一):RDD的原理与源码分析 >讲解过: 为了有效地实现容错,RDD提供了一种高度受限的共享内存,即RDD是只读的,并且只能通过其他RD ...
- 深入理解Spark 2.1 Core (八):Standalone模式容错及HA的原理与源码分析
第五.第六.第七篇博文,我们讲解了Standalone模式集群是如何启动的,一个App起来了后,集群是如何分配资源,Worker启动Executor的,Task来是如何执行它,执行得到的结果如何处理, ...
最新文章
- 洛谷P2312解方程
- 001_ZooKeeper简介
- html一条横线在文本旁边_lt;delgt; | HTML删除标记标签
- python梯度下降法实现线性回归_梯度下降法的python代码实现(多元线性回归)
- SQL Server 2005登录名,用户名,角色,架构之间的关系
- 【Java从入门到头秃专栏 】(二) 注释 数据类型 变量 常量 关键字 标识符 运算符 输入输出
- 一开机未通过输入密码登录,就出现用户名或密码错误??
- 了解字体以及字体安装
- 种子软件下载种子慢怎么解决
- Direct3D11学习经历分享
- OpenCV定位二维码的三个定位点
- redhat 复制文件夹及子文件夹_linux如何复制文件夹和移动文件夹
- wps中下划线怎么也去不掉_wps下划线怎么打(wps空白下划线怎么打不出来)
- 机器学习总结(二):梯度消失和梯度爆炸
- 【数据结构】代码重现
- python给图片加动态特效_一张图片能加动画特效,还能加背景音乐?Python制作雪景图...
- stm32h7 串口idle_【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现
- 分布式文件系统之GPFS
- NLP之淘宝商品评论情感分析
- 乐见Safengine licensor终于有了脱壳脚本