[bytebuddy实现原理分析 &源码分析 (一)](https://blog.csdn.net/wanxiaoderen/article/details/107079741) bytebuddy实现原理分析 &源码分析 (二) bytebuddy实现原理分析 &源码分析 (三) bytebuddy实现原理分析 &源码分析 (四)
dep 是byte buddy的开发包,实现都在里面。
源码非常复杂,而官方文档过于老旧,想要更好的使用 byte buddy 需要阅读源码。
源码的编写是递进的,从对java的类型进行封装,到类的动态定义,运行时的加载,以及如何匹配修改字节码。 但是作者明显是对jvm的知识非常了解,作者很清楚字节码在jvm的加载原理 & 很清楚添加或减少类的成员,到底应该对字节码做些什么,所以很难要从这个角度去分析源码。
所以鉴于水平有限,尽可能的一层层自下而上:从java类型的描述(description),到类型的缓存池(typepool),动态生成的类型(dynamci), 封装字节码的生成(implemetation),用来匹配目标的类或者方法(matcher),切面相关的类(asm.advice),java agent的构造器(agent.builder)的类定义的梳理,此外,提供一些高阶用法的讲解。
对于上面的提到的模块,会先讲整体的结构,其次分析从类的成员,一般弄清楚类的作用。
最后希望对byte buddy 有所了解,但又对源码望而却步的人来说,有点帮助。
如果第一次接触想要直接用起来,建议看官方文档。
pkg 代表 标题是 包 cls 代表 标题是类或接口 in 代表 cls定义内部的类。 impl 代表 继承于或者实现cls的类 doc 代表额外的说明
bytebuddy入门 这个入门其实写的不好,是笔记性质,给我自己看的。但是可取的
是花了一张图,罗列了bytebuddy的源码结构和各种类的作用。让使用bytebuddyAPI中 对各种参数类型作用感到困惑的人有帮助罗列了我所看到的资料:官网的介绍,bytebuddy作者写这个框架出发点,代码demo。从我个人来看,bytebuddy并不是针对java开发新手写的文档。对类的修改和植入,以及最终如何让应用到APM产品(比如skywalking产品)。我总结的学习步骤如下
字节码的知识—>ASM的知识—>JVM一些知识(类的加载,运行,卸载,类型)—> JNI(调用使用第三方代码) —> jdk attach的原理 —> jdk.instrument 知识 —> bytebuddy的知识。
这是我个人整理的一些资料
字节码知识 《深入理解字节码》asm指导手册 & asm入门JVM 比较零散,核心是classloader的加载机制,Class类详解JNI 理解机制就可以jdk.attach 原理 也是理解就可以了,本身不复杂instrument 实践一下,也不复杂都有demo整体的讲解是自下而上,一层一层递进着讲的。后面的内容的理解依赖于前面的内容。
pkg 这里重点讲bytebuddy如何封装类型的,这个是其他模块的基石。
最好具备
字节码的知识,以及Class类详解的知识从bytebuddy开发指南了解到源码的组织结构代码位置 在模块byte-buddy-dev/description包是有关类型的描述 Class类详解中有更详细的介绍 java class的所有类型都被封装了。包括如下
名称描述备注modifier修饰符,本质是一个2byte的int,代表一个方法的的描述。比如 public 对应的是 0X0001type类的类型,比如泛型,非泛型的封装annotation注解fieldfiled成员的封装method方法成员的封装enumeration枚举的封装先讲modifier 是修饰符相关API的封装, 上层接口是对操作字节码元素的上层封装 annotation注解是java注解的封装 field、method、enumeration就简略的说明结构,依照者前面的梳理可以理解。
bytebuddy的代码比较整齐,所有的类基本都是如下的模式。
package bytecode.annotation; public interface ByteBuddyTemplate { // 接口层 public void method(); // 抽象层 public abstract class AbstractBase implements ByteBuddyTemplate { public void method() { System.out.println("base"); } } // 实现层 public static class sub01 extends AbstractBase{ @Override public void method() { super.method(); System.out.println("sub01"); } } public static class sub02 extends AbstractBase { @Override public void method() { super.method(); System.out.println("sub02"); } } // 实现层- 常用枚举模式 enum sub03 implements ByteBuddyTemplate{ INSTANCE; @Override public void method() { } } }基本上有三层结构
1层 功能定义 interface2层 抽象实现 abstractbase3层 具体实现 & 经常用枚举 impl好处是有个清晰的结构,从接口的定义出发,再细致到具体的实现,都在一个类中实现。缺点是类很臃肿,一个类基本都是1000+或者10000+行。
class 以及class成员的字节码都有u2 access_flags字段,modifier就是代表着access_flags。
修饰符的核心类,内包含几个实现,复合1.1的设计模式 会讲明白,bytebuddy是如何定义修饰符的,耐心看。 核心方法
public interface ModifierContributor { /** * 空的mask */ int EMPTY_MASK = 0; /** * 获取mask,mask是有效位数 */ int getMask(); /** * 获取屏蔽位 */ int getRange(); /** * 这个是否是最初的 modifier */ boolean isDefault(); }示意图 标准的access_flags只有16bit,但是asm提供了超过16位的标识,这个只对asm有用,asm 写入字节码时,会讲多余的忽虑掉。
比如field字段有意义的标识符只有9位(asm中是10位)。使用mask可以添加没有的标志符
这个是field的mask
// Opcodes.ACC_PUBLIC 就是public 0x0001 int MASK = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE | Opcodes.ACC_DEPRECATED | Opcodes.ACC_ENUM | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT | Opcodes.ACC_VOLATILE;mask是规范中有效的位数,作用是充当掩码,去除不相关的位 range是额外的屏蔽位。
public int resolve(int modifiers) { for (T modifierContributor : modifierContributors) { modifiers = (modifiers & ~modifierContributor.getRange()) & modifierContributor.getMask(); } return modifiers; } 说明函数传入一个 modifier先进行 ~modifierContributor.getRange()运算。加入你要屏蔽掉ACC_PUBLIC 0X0001。那么取反码,第一位就变成了0,其余为1,结果为二进制11111111111111111111111111111110第二步(modifiers & ~modifierContributor.getRange()),public的位置就变成了0。最后才是或 & mask。所以range的作用是屏蔽位
示例 下面是自定义的一个ModifierContributor实现。DemoModifierContributor.Resolver. resolve(int modifier))是根据ModifierContributor对传入的modifier做修改。
比如 demo 的修饰符public static 代表着 1001先用屏蔽位range处理(1001 & ~0001) ,结果是 1000在用mask 补充额外的位数 1000|1010 ,结果是 1010所以rang代表屏蔽,mask代表额外的添加
public class DemoModifierContributor implements ModifierContributor { public static String demo = "hello;"; @Override public int getMask() { // 仅有前四位有效的mask 即 0000000000001010 int MASK = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; return MASK; } @Override public int getRange() { // 屏蔽调 public return Opcodes.ACC_PUBLIC; } @Override public boolean isDefault() { return false; } public static void main(String[] args) throws Exception{ // 打印demo 的modifier int modifier = DemoModifierContributor.class.getDeclaredField("demo").getModifiers(); System.out.println("origin modifier of demo : "+ Integer.toBinaryString(modifier)); // mask DemoModifierContributor demo = new DemoModifierContributor(); System.out.println("mask : "+ Integer.toBinaryString(demo.getMask())); // range System.out.println("range : "+ Integer.toBinaryString(demo.getRange())); // resolver 用来获取 有效的 modifier List<ModifierContributor> list = Collections.singletonList(new DemoModifierContributor()); DemoModifierContributor.Resolver resolver = DemoModifierContributor.Resolver.of(list); // 获取 (modifiers & ~modifierContributor.getRange()) | modifierContributor.getMask(); // (1001 & ~0001)|1100 --> (1001 & 1110)|1010 --> 1000|1010 --> 1010 System.out.println( Integer.toBinaryString(resolver.resolve(modifier))); } } //打印 origin modifier of demo : 1001 mask : 1010 range : 1 1010内置了四个子接口ForField,ForMethod,ForType,ForParameter。 通过了解自定义ModifierContributor的子类。就很好理解四个实现,很类似。比如 仅仅是定义了10位mask
interface ForField extends ModifierContributor { /** * A mask for all legal modifiers of a Java field. */ int MASK = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE | Opcodes.ACC_DEPRECATED | Opcodes.ACC_ENUM | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT | Opcodes.ACC_VOLATILE; }工具类,定义了处理ModifierContributor的方法。 类的声明 class Resolver<T extends ModifierContributor>,携带了一个ModifierContributor对象。
构造器 创建时接受多个ModifierContributor
public static <S extends ModifierContributor> Resolver<S> of(Collection<? extends S> modifierContributors) { return new Resolver<S>(modifierContributors); }核心方法 使用时,调用resolve获取处理过的modifiers。
public int resolve(int modifiers) { for (T modifierContributor : modifierContributors) { modifiers = (modifiers & ~modifierContributor.getRange()) | modifierContributor.getMask(); } return modifiers; }这个包里面接口无一例外都是实现ModifierContributor的类 挑一个说明FieldManifestation就是来判断一个Field是否被final,volatile或者修饰,
public enum FieldManifestation implements ModifierContributor.ForField { PLAIN(EMPTY_MASK), FINAL(Opcodes.ACC_FINAL), VOLATILE(Opcodes.ACC_VOLATILE); private final int mask; FieldManifestation(int mask) { this.mask = mask; } public int getMask() { return mask; } public int getRange() { return Opcodes.ACC_FINAL | Opcodes.ACC_VOLATILE; } public boolean isDefault() { return this == PLAIN; } public boolean isFinal() { return (mask & Opcodes.ACC_FINAL) != 0; } public boolean isVolatile() { return (mask & Opcodes.ACC_VOLATILE) != 0; } public boolean isPlain() { return !isFinal() && !isVolatile(); }上面讲了修饰符的各种实现类,ModifierReviewable是一个汇总类。 这个类理解起来不复杂,比如 ForFieldDescription内置了一堆方法来判断针对field的修饰符。
interface ForFieldDescription extends OfEnumeration { /** * Specifies if the modifier described by this object */ boolean isVolatile(); /** */ boolean isTransient(); /** */ FieldManifestation getFieldManifestation(); /** */ FieldPersistence getFieldPersistence(); }实现了ForFieldDescription的AbstractBase,看看如何实现isVolatile()。 可以看出就是 和Opcodes.ACC_VOLATILE 0x0040;去 &,看看相应位是否设为1
abstract class AbstractBase implements ForTypeDefinition, ForFieldDescription, ForMethodDescription, ForParameterDescription { public boolean isVolatile() { return matchesMask(Opcodes.ACC_VOLATILE); } private boolean matchesMask(int mask) { return (getModifiers() & mask) == mask; }字节码的二进制中每个块都有对应的名称。NamedElement就代表了这个结构,方便的获取块的名称。以下是介绍各个函数的作用,以及给了一个实现类 ,查看调用的效果。
public interface NamedElement { /** * element 没有名称 */ String NO_NAME = null; /** * 源码中没有名称 */ String EMPTY_NAME = ""; /** * 源码中的真实名称,如果没有的话,就会返回EMPTY_NAME 。比如`pubclic * String demo`,demo就是真实名称 */ String getActualName();在NamedElement中实现NamedElement 的内置接口
名称接口WithRuntimeNameString getName()返回对于running Java application可见的字节码中的名称java runtime期间,被命名单元的名称getInternalName()返回Java class file format中可见的字节码名称WithOptionalName标志字节码元素是否被明确命名WithGenericNametoGenericString()字节码元素的泛型名称泛型风格的名称WithDescriptorgetDescriptor()返回字节码元素的描述符返回文件描述符和泛型签名getGenericSignature()如果是泛型就返回泛型名称从一个实现看上面的作用 ForLoadedField是实现了NamedElement的三个接口
public class NamedElementTest { public String demo = "one"; public void print() throws Exception { Field demoField = NamedElementTest.class.getDeclaredField("demo"); FieldDescription.ForLoadedField loadedField = new FieldDescription.ForLoadedField(demoField); System.out.println("getActualName() : " + loadedField.getActualName()); // 实现了NamedElement中的三个内置实现 System.out.println("impl form WithRuntimeName : " + (loadedField instanceof NamedElement.WithRuntimeName)); System.out.println("getName() : " + loadedField.getName()); System.out.println("getInternalName() : " + loadedField.getInternalName()); System.out.println("impl form WithDescriptor : " + (loadedField instanceof NamedElement.WithDescriptor)); System.out.println("getName() : " + loadedField.getDescriptor()); System.out.println("getGenericSignature() : " + loadedField.getGenericSignature()); System.out.println("impl form WithGenericName : " + (loadedField instanceof NamedElement.WithGenericName)); System.out.println("toGenericString() : " + loadedField.toGenericString()); System.out.println("impl form WithOptionalName : " + (loadedField instanceof NamedElement.WithOptionalName)); } public static void main(String[] args) throws Exception { new NamedElementTest().print(); } }打印
getActualName() : demo impl form WithRuntimeName : true getName() : demo getInternalName() : demo impl form WithDescriptor : true getName() : Ljava/lang/String; getGenericSignature() : null impl form WithGenericName : true toGenericString() : public java.lang.String bytebuddys.NamedElementTest.demo impl form WithOptionalName : false返回类型的定义,TypeDefinition后面会讲到,这个接口也是最上层的接口之一。 TypeDefinition 是bytebuddy对java 原生Type的封装,也是代表了类的类型。
public interface DeclaredByType { TypeDefinition getDeclaringType(); }顺理成章讲到这个类,这个类继承了上面讲到的所有类,除了AnnotationSource暂时理解为封装注解的类,用来处理注解。后面会在介绍注解时会详细介绍。
ByteCodeElement从单词的字面就可以理解,这就代表这一个 字节吗的一个元素,实现可以是field,或者method,等。 核心的方法
名称接口boolean isVisibleTo(TypeDescription typeDescription)对于传入的类型是否可见,比如同级的classLoader加载类,就互相看不见boolean isAccessibleTo(TypeDescription typeDescription)对于传入的类型是否权限访问,比如其他类看不见privite举例说明
DemoModifierContributor.class和ByteCodeElementTest.class在同一个包。 ByteCodeElementTest有两个方法public和privite。DemoModifierContributor 可以看见和访问public, 但是看不见privite。 ForLoadedMethod是ByteCodeElement的一个实现。下来就来展示看一下。
package bytebuddys; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import java.lang.reflect.Method; public class ByteCodeElementTest { private void notSee() { } public void canSee() { } public void test() throws Exception { Method notsee = ByteCodeElementTest.class.getDeclaredMethod("notSee"); Method cansee = ByteCodeElementTest.class.getDeclaredMethod("canSee"); MethodDescription.ForLoadedMethod notSeeBD = new MethodDescription.ForLoadedMethod(notsee); MethodDescription.ForLoadedMethod canSeeBD = new MethodDescription.ForLoadedMethod(cansee); // 同包下的另外一个类 TypeDescription samePkgAnotherClass = TypeDescription.ForLoadedType.of(DemoModifierContributor.class); System.out.println("samePkgAnotherClass cant see privite : " + notSeeBD.isVisibleTo(samePkgAnotherClass)); System.out.println("samePkgAnotherClass cant use privite : " + notSeeBD.isAccessibleTo(samePkgAnotherClass)); System.out.println("samePkgAnotherClass can see public : " + canSeeBD.isVisibleTo(samePkgAnotherClass)); System.out.println("samePkgAnotherClass can use public : " + canSeeBD.isAccessibleTo(samePkgAnotherClass)); } public static void main(String[] args) throws Exception{ new ByteCodeElementTest().test(); } }打印
samePkgAnotherClass cant see privite : false samePkgAnotherClass cant use privite : false samePkgAnotherClass can see public : true samePkgAnotherClass can use public : true这两个类位于ByteCodeElement,但是和ByteCodeElement没有继承或者实现关系。
Token,其实就是代表真实的字节码。熟悉ASM的不会陌生。ASM的CoreAPI接受ClassVister便利class字节码。Token也是类似的,被用来便利字节码。 一个Token就是一个字节码元素。
/** * A token representing a byte code element. * * @param <T> The type of the implementation. */ interface Token<T extends Token<T>> { /** * Transforms the types represented by this token by applying the given visitor to them. * * @param visitor The visitor to transform all types that are represented by this token. * @return This token with all of its represented types transformed by the supplied visitor. */ T accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor); /** * A list of tokens. * * @param <S> The actual token type. */ class TokenList<S extends Token<S>> extends FilterableList.AbstractBase<S, TokenList<S>> { /** * The tokens that this list represents. */ private final List<? extends S> tokens; /** * Creates a list of tokens. * * @param token The tokens that this list represents. */ @SuppressWarnings("unchecked") public TokenList(S... token) { this(Arrays.asList(token)); } /** * Creates a list of tokens. * * @param tokens The tokens that this list represents. */ public TokenList(List<? extends S> tokens) { this.tokens = tokens; } /** * Transforms all tokens that are represented by this list. * * @param visitor The visitor to apply to all tokens. * @return A list containing the transformed tokens. */ public TokenList<S> accept(TypeDescription.Generic.Visitor<? extends TypeDescription.Generic> visitor) { List<S> tokens = new ArrayList<S>(this.tokens.size()); for (S token : this.tokens) { tokens.add(token.accept(visitor)); } return new TokenList<S>(tokens); } @Override protected TokenList<S> wrap(List<S> values) { return new TokenList<S>(values); } /** * {@inheritDoc} */ public S get(int index) { return tokens.get(index); } /** * {@inheritDoc} */ public int size() { return tokens.size(); } } } 注意interface Token<T extends Token<T>> 意味着这个是链式的,一个token继承着上一个token。意味着一个Token,前面有很多限定条件,他必须是谁的子类,这样你能精确识别Token。accept 方法,接受一个visitor,作为下一个类型的参数。这是访问者模式。访问者模式 Vistitor 我要去拜访朋友。 我有两个爱好,喝酒和抽烟。 小李是我的同学,他爱喝酒。当他问我爱什么,我知道要说喝酒,才能对主人礼貌。 小王是我的同事,他爱抽烟。当他问我爱什么,我知道要说抽烟,才能对主人礼貌 Host 本地有个待客的习惯,当有客人来时要问,你喜欢什么。 小李和小王都懂这个礼貌。 除此之外 小李,在询问前,要先拥抱一下 小王,在询问前,要握手 … 进一步分析,访作为访问者的我,定义了到访不同客人家要执行的行为。 作为主人的小王和小李,仅仅需要接待我,进行礼貌的询问。
package bytebuddys; public class VisitorMode { // 拜访者 public interface Visitor { public void visit(WangHost wangHost); public void visit(LiHost liHost); } public static class MeVisitor implements Visitor { @Override public void visit(WangHost wangHost) { System.out.println(" 抽根烟"); } @Override public void visit(LiHost liHost) { System.out.println(" 来点酒"); } } // 主人 public interface Host { // 接待 public void accept(Visitor v); // 询问 default public void ask() { System.out.println(" 欢迎,你想要点什么?"); } } public static class WangHost implements Host { public void accept(Visitor v) { System.out.println("握手"); ask(); v.visit(this); } } public static class LiHost implements Host { public void accept(Visitor v) { System.out.println("拥抱"); ask(); v.visit(this); } } public static void main(String[] args) { System.out.println("风和日丽的一天 我作为客人"); Visitor me = new MeVisitor(); System.out.println("上午去了小王家,得到了热情的 款待"); Host wang = new WangHost(); wang.accept(me); System.out.println("下午去了小李家,得到了热情的 款待"); Host li = new LiHost(); li.accept(me); } } 打印 风和日丽的一天 我作为客人 上午去了小王家,得到了热情的 款待 握手 欢迎,你想要点什么? 抽根烟 下午去了小李家,得到了热情的 款待 拥抱 欢迎,你想要点什么? 来点酒当然Token的更复杂,这个设计可以让token像金字塔,一层套一层。接受一个类型作为参数 ,使用accept,返回下一个token=当前token+传入类型。
Dependant —> 受扶养者 一个复合类。充当ElementMatcher和Token的桥梁
interface TypeDependant<T extends TypeDependant<?, S>, S extends ByteCodeElement.Token<S>> { /** * Returns this type dependant in its defined shape, i.e. the form it is declared in and without its type variable's resolved. * * @return This type dependant in its defined shape. */ T asDefined(); /** * Returns a token representative of this type dependant. All types that are matched by the supplied matcher are replaced by * {@link net.bytebuddy.dynamic.TargetType} descriptions. * * @param matcher A matcher to identify types to be replaced by {@link net.bytebuddy.dynamic.TargetType} descriptions. * @return A token representative of this type dependant. */ S asToken(ElementMatcher<? super TypeDescription> matcher); } asDefined() 比如一个类 TypeDependantImpl implments TypeDependant。 那么asDefined()就是返回TypeDependantImpl的作用,就是返回定义的类asToken 就是把一个ElementMatcher 变成Token, ElementMatcher后面会说TypeVariableSource代表了字节码的类型。 TypeDefinition代表了一个类型的定义,真实的定义要么是TypeDescription,要么是TypeDescription.Generic。 TypeDescription集成TypeDefinition,代表了封装了具体的类型实现
首先java中有一个TypeVariable,这个是泛型中的变量,比如List<T>。这个T就是TypeVariable。 Byte Buddy 中的TypeVariableSource和TypeVariable并不对应。这里含义更广:代表了 code element 的类型。 实现了Modifier的接口,具备了判断access_flags的能力 继承下来判断修饰符的方法 比如
名称描述isPublic()isAbstract()isStatic()新添加的方法 isInferrable
名称描述isInferrable()类型是否可以被动态的推断。就是类型可不可义在运行过程中生成,或者更改isGenerified()是否是泛型。这个类本身被声明为泛型格式class List<T>,或者内部的类是泛型,那么都会为真TypeList.Generic getTypeVariables()返回内部的泛型TypeVariableSource getEnclosingSource()获取外部的包裹,Enclosing,Enclosing指的就是类型位于的位置。Enclosed就是内部方法和类型TypeDescription.Generic findVariable(String symbol)根据符号,在TypeVariableSource中查找匹配的类型accept(Visitor<T> visitor)访问者模式,visitor中封装着处理TypeVariableSource的逻辑。TypeVariableSource UNDEFINED= null;不是方法,UNDEFINED就代表着空的TypeVariableSource示例
先重点说一下getEnclosingSource(),就是获取外层的包裹类或者方法。 java 自身的getEnclosingMethod()和getEnclosingClass()是一样的效果。 getEnclosingSource()相当于两者之和,这个是在匿名类时特别有用。
public class TypeVariableSourceTest { public <T extends Food> T makeFood(T food) { return food; } public Object makeSauce() { class Sauce { public void print() { } } return new Sauce(); } // 食物 public static class Food { } public static void main(String[] args) throws Exception { Method makeFood = TypeVariableSourceTest.class.getDeclaredMethod("makeFood", Food.class); MethodDescription.ForLoadedMethod makeFoodBD = new MethodDescription.ForLoadedMethod(makeFood); System.out.println("Enclosing 外围的包裹类" + makeFoodBD.getEnclosingSource().toString()); TypeDescription.ForLoadedType sauceBD = new TypeDescription.ForLoadedType( new TypeVariableSourceTest().makeSauce().getClass()); System.out.println("Enclosing 外围的包裹方法" + sauceBD.getEnclosingSource()); } }makeFood方法定义在类内,他的包裹类就是TypeVariableSourceTest makeSauce方法产生了一个匿名类,对这个匿名类而言,他的包裹方法就是makeSauce方法 打印
Enclosing 外围的包裹类或者方法class bytebuddys.TypeVariableSourceTest Enclosing 外围的包裹类public java.lang.Object bytebuddys.TypeVariableSourceTest.makeSauce()其他方法参照着下面的使用理解
ForLoadedMethod是一个实现类之一
System.out.println("Inferrce 可否动态修改: " + makeFoodBD.isInferrable()); System.out.println("isGenerified() 是否或者包含泛型: " + makeFoodBD.isGenerified()); System.out.println("Enclosing 外围的包裹类或者方法: " + makeFoodBD.getEnclosingSource().toString()); System.out.println("getTypeVariables() 获取泛型: " + makeFoodBD.getTypeVariables().toString()); System.out.println("Enclosing 外围的包裹类或者方法: " + makeFoodBD.findVariable("T").toString());打印
Inferrce 可否动态修改: true isGenerified() 是否或者包含泛型: true Enclosing 外围的包裹类或者方法: class bytebuddys.TypeVariableSourceTest getTypeVariables() 获取泛型: [T] Enclosing 外围的包裹类或者方法: T就是类型的定义,所有类型的上层接口。提供方便的转化方法
方法描述TypeDescription.Generic asGenericType()转化为泛型TypeDescription asErasure()擦出类型TypeDescription.Generic getSuperClass()返回父类TypeList.Generic getInterfaces()返回实现的接口类型FieldList<?> getDeclaredFields()返回Fileld类型MethodList<?> getDeclaredMethods()返回method类型TypeDefinition getComponentType()返回数组的类型,比如String[],就返回代表String的类型RecordComponentList<?> getRecordComponents();record是jdk 14新特性,类似C的StructString getTypeName()record是jdk 14新特性,类似C的StructStackSize getStackSize()栈帧大小boolean represents(Type type);传入对象是否相等Sort getSort()返回对象代表的一堆类型,Sort是个集合对象类型的常量的集合
名称描述NON_GENERIC非泛型GENERIC_ARRAY泛型数组PARAMETERIZED参数WILDCARDWildcardType是Type的子接口,用于描述形如“? extends classA” 或 “?super classB”的“泛型参数表达式”。List<? extends String>这种类型就叫WildcardTypeVARIABLE代表了被绑定到net.bytebuddy.description.TypeVariableSource上的类型VARIABLE_SYMBOLIC代表了一个类型,但是仅仅是一个符号,并不会被绑定到net.bytebuddy.description.TypeVariableSource public static TypeDescription.Generic describe(Type type) 将一个java中Tpye类型,转化为 TypeDescription.Generic 。这个方法很关键提供了这样的转化。 转化的实现 protected static TypeDescription.Generic describe(Type type, TypeDescription.Generic.AnnotationReader annotationReader) { if (type instanceof Class<?>) { return new TypeDescription.Generic.OfNonGenericType.ForLoadedType((Class<?>) type, annotationReader); } else if (type instanceof GenericArrayType) { return new TypeDescription.Generic.OfGenericArray.ForLoadedType((GenericArrayType) type, annotationReader); } else if (type instanceof ParameterizedType) { return new TypeDescription.Generic.OfParameterizedType.ForLoadedType((ParameterizedType) type, annotationReader); } else if (type instanceof TypeVariable) { return new TypeDescription.Generic.OfTypeVariable.ForLoadedType((TypeVariable<?>) type, annotationReader); } else if (type instanceof WildcardType) { return new TypeDescription.Generic.OfWildcardType.ForLoadedType((WildcardType) type, annotationReader); } else { throw new IllegalArgumentException("Unknown type: " + type); } }类型描述类,也是承载具体类型的定义 Field 预定义的几种类型。
名称描述OBJECTForLoadedType(Object.class)STRINGForLoadedType(String.class)PARAMETERIZED参数CLASSForLoadedType(Class.class)THROWABLEForLoadedType(Throwable.class)VOIDForLoadedType(void.class)ARRAY_INTERFACESTypeList.Generic.ForLoadedTypes(Cloneable.class, Serializable.class)UNDEFINEDTypeDescription UNDEFINED = null方法 这里调从字面意思不容易看出来的
名称描述boolean isInstance(Object value)是否是给定对象的实例boolean isAssignableFrom(Class<?> type)是否是从当前对象继承而来,包括当前。Foo和class Bar extends Foo,Foo.class.isAssignableFrom(Bar.class)返回trueboolean isAssignableTo(Class<?> type)刚好相反boolean isInHierarchyWith(Class<?> type)一个类是否在另一个类的继承体系中MethodDescription.InDefinedShape getEnclosingMethod()外层的方法getActualModifiers(boolean superFlag)返回class file中的修饰符boolean isAnonymousType()是否是匿名类boolean isMemberType()是否是成员类型,Field、method、constructorsPackageDescription getPackage()获取包的描述符AnnotationList getInheritedAnnotations()获取继承的注解int getInnerClassCount()获取内部类的数量TypeDescription asBoxed()装箱类,IntgerTypeDescription asUnboxed()拆箱类,int泛型。 可以看到这个泛型非常之复杂。但是确实很有必要去将每一个部分。 前面一层一层不厌其烦的描述一些类的方法和作用。就是为了理解现在这个类,这个类又对理解后面的类很重要。 但是重点是理解,并不是全部罗列。
之前解释Visitor,就是让大家明白。visitor是真正定义动作的地方。
比如这里NoOp就是统一对类型不加工,传入什么样返回什么样。TypeErasing类型擦除,对传入的类型进行类型参数,遇到wildcard type抛出异常AnnotationStripper 类型注解去除,去除一个类型所有注解类型Assigner 这里可以翻译为,归属于。前面也有isAssignableFrom,就是用来判断两个类型之间的关系。怎么判断呢,他用了一个模式分发,Dispatcher,逐层判断分发。Validator 校验,用来在class file 中校验格式是否正确Reifying 纠正,如果一个类代码字节中的原本的类型(raw types),就是来纠正ForSignatureVisitor追加类型签名,将新的类型签名,添加到classfile文件中Substitutor代替,用来代替原始的类型 内部实现 WithoutTypeSubstitution:允许替换泛型,而不是非泛型。 ForAttachment :定位一个成员,并且替代旧成员,后面操作这个成员 ForDetachment: 从他的declaration context中取消某个变量的锚定,这个是通过ElementMatcher提供的targetType来去匹配的。 ForTypeVariableBinding: 为类型绑定一个值,赋值 ForTokenNormalization: 用一个符号Token代替所有的targetType位置。ForRawType 代表原始类型,类似于擦除。比如List<String>就被会被转化为原始的类型List<T>Reducing 接受参数,擦除与参数匹配的类型反射详解中提到了,除了Type类型之外,java还提供了一种注解形式的类型,运行是标注在类型。 AnnotationType。传统的JVM依旧靠着解析Type类型来处理class的类型。但是新的JVM依靠注解来驱动类型的解析。 名词解释 之前了解了这么多例子,一般说说一个泛型? extend T,或者一个类型class extends A implments。 所以函数中出现 LowerBound就是获取下边界的类型,upperBoundType就是获取上边界。 resolve是找到的意思 ofXXX就是创建一个类动作 ForXX是某个接口的子类之一,针对谁的实现
Dispatcher : in
ForLegcayVm implments Dispatcher 就是传统解析类的,比如识别一个接口类型
public AnnotationReader resolveInterfaceType(Class<?> type, int index) { return NoOp.INSTANCE; }ForJava8CapableVm implments Dispatcher CreateAction 就是一个穿件ForJava8CapableVm的工厂,调用run,就产生一个ForJava8CapableVm
Delegator : in 委托模式,是个抽象类,实现了AnnotationReader,对每个方法委托调用 比如
public AnnotationReader ofWildcardUpperBoundType(int index) { return new ForWildcardUpperBoundType(this, index); } protected ForWildcardUpperBoundType(AnnotationReader annotationReader, int index) { // 委托 super(annotationReader); this.index = index; }TypeDescription的内置实现类之一。为了描述类型。为了加速,预制了这么多基本类型
对于数组,比如String[] 类型的映射。代表了一个数组类型。注意数组不是集合。
一个潜在的类型描述。对于一个没有任何方法和任何field的类。可以想像得到创建一个类的时候,首先会创建一个空的对象。这个对象就是Latent.
代表了一个包的描述
代表了这样的类型:总是企图去加载委托类的父类。 名字也可以看出来,就是代表加载super类型的工具类的类型。
内置一个List包含对类型的集合。字节码中,很多区域是变长的。比如method区域,他的本地变量大小,和栈深(stacksize)是编译时计算出来的。 这就导致一个问题,我要插入一个方法,我必须要计算前面方法的本地变量表和栈深,进行累加,然后确认新method插入的位置。 Byte buddy提供了一个结构TypeList,可以将多个类型组合,划分为一组。可以得到总的StakSize。这样我仅需和一个TypeList比较,而不是挨个计算。
举例 计算一组变量的stackSize
public int getStackSize() { return StackSize.of(this); } // 进行累加 public static int of(Collection<? extends TypeDefinition> typeDefinitions) { int size = 0; for (TypeDefinition typeDefinition : typeDefinitions) { size += typeDefinition.getStackSize().getSize(); } return size;描述包的结构的类,典型的会在每个包底下创建package_info.java来存放包的信息。
核心的方法
boolean contains(TypeDescription typeDescription); 判断某个包内是否包含给定的类型常用的实现类是
ForLoadedPackage ,以下是一个示例。判断类的包含关系。 public class ForLoadPkg { public static void main(String[] args) { PackageDescription.ForLoadedPackage pkg = new PackageDescription.ForLoadedPackage(ForLoadPkg.class.getPackage()); TypeDescription.ForLoadedType thisclass = new TypeDescription.ForLoadedType(ForLoadPkg.class); TypeDescription.ForLoadedType anotherclass = new TypeDescription.ForLoadedType(Enums.Demo.class); System.out.println("package name" + pkg.getName()); System.out.println(pkg.getName() + " contains : " + thisclass.getName() + " : " + pkg.contains(thisclass)); System.out.println(pkg.getName() + " contains : " + anotherclass.getName() + " : " + pkg.contains(anotherclass)); } }打印
package namebytebuddys bytebuddys contains : bytebuddys.ForLoadPkg : true bytebuddys contains : Enums.Demo : false继承自 ByteCodeElement.Token 就是Token,真正的字节码 注意 getSymbol()是集成自Generic,是针对泛型的,非泛型使用会报错
System.out.println( TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(TypeVariableSourceTest.class).getSymbol() );打印
Exception in thread "main" java.lang.IllegalStateException: A non-generic type does not imply a symbol: class bytebuddys.TypeVariableSourceTest at net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType.getSymbol(TypeDescription.java:3804) at bytebuddys.TypeVariableSourceTest.main(TypeVariableSourceTest.java:45)通过对Type的描述,基本上会清晰的看出bytebuddy是如何对Type进行描述的。 这个类位于description.annotation是对原先的javaannotation进行了封装。 Annotation详解有关于注解的定义和使用。 对于一个注解而言,注解,和注解的赋值
代表了注解源码的模型
getDeclaredAnnotations 返回所有标注的注解,类似Class.getgetDeclaredAnnotations()。但是AnnotationList是一个集合,是Bytebuddy定义的,方便对一组注解管理,和之前的TypeList类似。Empty 枚举类,代表着没有注解Explicit 意思是明确的,后面还出现了很多Explicit,因为模糊就意味着无法准确的生成字节码。注解自身有一堆属性值,这个类就是 代表了一个未加载(unloaded)的注解值。这个值的类型可以是:
基本类型(int,long等,等同他们的包装类),Strings 类型和,基本类型的数组TypeDescription,或者一个TypeDescription数组EnumerationDescription,枚举类型或者一个这样的数组AnnotationDescription,注解类型或者一个这样的数组这个被代表的值不一定是要被找到的(有效的)。 比如可以包含一个不可获取的类型,未知的枚举类,或者不正确的注解。
注意接口定义public interface AnnotationValue<T, S> {}, 是一个泛型,T代表未加载(unloaded)的类型,S代表加载过(loaded)的类型。
枚举类,代表了值的类型
UNDEFINED未被定义,代表着注解的属性没有找到,并且抛出java.lang.annotation.IncompleteAnnotationExceptionUNRESOLVED代表着注解的属性者,不合法,但是处于一个异常状态RESOLVED被解决,并以找到的真实的value代表AnnotationValue的一个变形。代表一个需要被classLodaer去加载,但是还没有加载的一个类型。会抛出
java.lang.annotation.IncompleteAnnotationExceptio :注解的的变量没有赋值,也没有默认值,比如@Student{id=0}但是也要求{name="XXX"}时,没有赋值name的异常java.lang.EnumConstantNotPresentException : 不知道的枚举常量类型,比如用了定义之外的常量java.lang.annotation.AnnotationTypeMismatchException :annotation property类型不符合这个接口的实现必须要实现,Object.hashCode(),Object.toString(),因为这些都会被 java.lang.annotation.Annotation的实现用到。也必须要重新实现hashCode(),equals()和toString()确保两个相同的代表同样annotation value值的实例相等。
interface Loaded<U> { // 获取状态 State getState(); // 找到真实的注解value,找不到时抛出异常 U resolve(); // 判断两个实例是否是同一个类型 boolean represents(Object value); }注意这里的U 往往就是传入一个AnnotationValue类型
处理常量池的常量
一个Annotation可以作为另外一个Annotation的属性值。就是代表了成员注解的类型
ForEnumerationDescription :枚举类型 ForTypeDescription: Type类型 ForDescriptionArray: annotation的数组 ForMissingType: 无法加载的类型 ForMismatchedType: 类型不匹配 ForMissingValue: 没有值的注解属性,也没有定义default ForIncompatibleType: 代表了一个拥有异常的类型。
代表注解的类型。
RetentionPolicy getRetention(); 注解的策略 SOURCE,CLASS,RUNTIMESet<ElementType> getElementTypes(); 规定可以标注在什么类型的约束 TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE,TYPE_PARAMETER,TYPE_USEboolean isInherited(); 是否可被继承boolean isDocumented(); 是否被文档化java反射中的InvocationHandler。这个是封装注解的InvocationHandler
代表一个早已经加载完成的注解
代表一个定义明确,但是还没有load的注解
分为Method和Parameter 方法和参数类型。 Parameter是 java方法或者构造器的参数。
对于TypeDescription的缓存池。加载后的各种类型,可以放到Type pool中。这个类可以存放不实用classLoader加载的类。也就是说可以绕过jvm的记载机制直接从文件加载。
仅有两个方法
boolean isResolved(); 一个TypeDescription是否可以从文件中找到,TypeDescription resolve(); 从文件中加载,并返回这个TypeDescriptionByte Buddy的示例,从ClassPath生成一个Type.
class MyApplication { public static void main(String[] args) { TypePool typePool = TypePool.Default.ofClassPath(); new ByteBuddy() .redefine(typePool.describe("foo.Bar").resolve(), // do not use 'Bar.class' ClassFileLocator.ForClassLoader.ofClassPath()) .defineField("qux", String.class) // we learn more about defining fields later .make() .load(ClassLoader.getSystemClassLoader()); assertThat(Bar.class.getDeclaredField("qux"), notNullValue()); } }最新的API已经没有ofClassPath()了 ClassFileLocator 代表着类的位置
dynamicType是Byte Buddy对生成类的抽象。也是被用来操作和
用来查找类文件。
用来需找类,并返回类定义byte[]。LocationStrategy核心就是如何使用ClassFileLocator这个类。 内部11个实现类
orderreturn typemethod描述0Resolutionlocate(String name)根据类的名称,返回一个Resolution。Resolutiong包含类的信息,比如isResolved代表是否找到,byte[] resolve()返回类的字节码定义ClassFileLocator的内部接口Resolution代表类的二进制信息
orderreturn typemethod描述0booleanisResolved()检查是否存在这个类1byte[]resolve()找到类的定义,返回一个byte[]找不到时 Resolution的实现类 class Illegal implements Resolution。可以看到,获取类的二进制信息时直接抛出异常
/** * {@inheritDoc} */ public boolean isResolved() { return false; } /** * {@inheritDoc} */ public byte[] resolve() { throw new IllegalStateException("Could not locate class file for " + typeName); }找到时 Resolution的实现类 class Explicit implements Resolution。可以看到,获取类的二进制信息
/** class Explicit implements Resolution { /** * The represented data. */ private final byte[] binaryRepresentation; /** * Creates a new explicit resolution of a given array of binary data. * * @param binaryRepresentation The binary data to represent. The array must not be modified. */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract") public Explicit(byte[] binaryRepresentation) { this.binaryRepresentation = binaryRepresentation; } /** * {@inheritDoc} */ public boolean isResolved() { return true; } /** * {@inheritDoc} */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract") public byte[] resolve() { return binaryRepresentation; } }enum NoOp implements ClassFileLocator 找不到对象,返回Illegal Resolution
class Simple implements ClassFileLocator,查找类定的简单实现 Filed private final Map<String, byte[]> classFiles; 核心的Map ,一个由类名和 类定义,组成kv的ClassFileLocator。 method 只写几个,Simple体现在,函数都是接受类定义byte[],放入ClassFileLocator中 比如of方法
public static ClassFileLocator of(Map<TypeDescription, byte[]> binaryRepresentations) { Map<String, byte[]> classFiles = new HashMap<String, byte[]>(); for (Map.Entry<TypeDescription, byte[]> entry : binaryRepresentations.entrySet()) { classFiles.put(entry.getKey().getName(), entry.getValue()); } return new Simple(classFiles); }class ForClassLoader implements ClassFileLocator 需要给定classloader,在classloader中找类 Filed 核心就是这个 private final ClassLoader classLoader; method 核心方法locate,使用classloader寻找
protected static Resolution locate(ClassLoader classLoader, String name) throws IOException { InputStream inputStream = classLoader.getResourceAsStream(name.replace('.', '/') + CLASS_FILE_EXTENSION); if (inputStream != null) { try { return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream)); } finally { inputStream.close(); } } else { return new Resolution.Illegal(name); } }Module jDK9的特性,比classLoader更高一级 class ForModule implements ClassFileLocator
class ForJarFile implements ClassFileLocator 在jar包里面寻找class文件。 可以看到locate方法在jar里需找类的方法
public Resolution locate(String name) throws IOException { ZipEntry zipEntry = jarFile.getEntry(name.replace('.', '/') + CLASS_FILE_EXTENSION); if (zipEntry == null) { return new Resolution.Illegal(name); } else { InputStream inputStream = jarFile.getInputStream(zipEntry); try { return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream)); } finally { inputStream.close(); } } }从 module,Folder,ForURL中寻找class文件 PackageDiscriminating 是包
支持多个classFileLocator。 locate方法查找时,也是从classFileLocator中遍历查找优先,返回第一个。
/** * The {@link ClassFileLocator}s which are represented by this compound * class file locator in the order of their application. */ private final List<ClassFileLocator> classFileLocators; /** * Creates a new compound class file locator. * * @param classFileLocator The {@link ClassFileLocator}s to be * represented by this compound class file locator in the order of their application. */ public Compound(ClassFileLocator... classFileLocator) { this(Arrays.asList(classFileLocator)); } /** * Creates a new compound class file locator. * * @param classFileLocators The {@link ClassFileLocator}s to be represented by this compound class file locator in * the order of their application. */ public Compound(List<? extends ClassFileLocator> classFileLocators) { this.classFileLocators = new ArrayList<ClassFileLocator>(); for (ClassFileLocator classFileLocator : classFileLocators) { if (classFileLocator instanceof Compound) { this.classFileLocators.addAll(((Compound) classFileLocator).classFileLocators); } else if (!(classFileLocator instanceof NoOp)) { this.classFileLocators.add(classFileLocator); } } } /** * {@inheritDoc} */ public Resolution locate(String name) throws IOException { for (ClassFileLocator classFileLocator : classFileLocators) { Resolution resolution = classFileLocator.locate(name); if (resolution.isResolved()) { return resolution; } } return new Resolution.Illegal(name); }负责加载类的包
负责定义包的属性,当一个包中的类被刚加载时。也可以选择在定义一个包时,不做什么。
Definition define(ClassLoader classLoader, String packageName, String typeName); Definition是 包的数据结构体,新定义的包会返回一个这样的对象。包的定义 默认的实现
NoOp 什么也不干,方法会报错。2 Trivial 仅仅是定义了包,没有任何元数据3 Simple 一个简单实现,所有的属性都是 String value。仅仅返回Define的两个实现
enum NoOp implements PackageDefinitionStrategy { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Definition define(ClassLoader classLoader, String packageName, String typeName) { return Definition.Undefined.INSTANCE; } } enum Trivial implements PackageDefinitionStrategy { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Definition define(ClassLoader classLoader, String packageName, String typeName) { return Definition.Trivial.INSTANCE; } }读取jar包的META-INF/MANIFEST.MF
教程。就是新定义的类如何被动态的加载 重要点是理解
WRAPPER(默认会选择) 以传入的classLoader为parent,创建一个新的net.bytebuddy.dynamic.loading.ByteArrayClassLoader去加载。 字节数组类加载器(ByteArrayClassLoader)知道任何动态创建的类型,并且可以以本地方法加载给定的类。由于字节数组类加载器是在每个遇到的未知类上查询的,因此这允许加载具有循环加载时间相关性的类。由于对字节数组类加载器加载的类的封装,一旦该类加载器,其类或这些类的任何实例变得不可访问,此策略将导致这些类的卸载。WRAPPER_PERSISTENT 和WRAPPER相同,但是从创建net.bytebuddy.dynamic.loading.ByteArrayClassLoaderCHILD_FIRST、CHILD_FIRST_PERSISTENT 和WRAPPER类似,但是打破双亲委派,直接加载,而不是从父类寻找INJECTION 注入,不去创建新的classloader。直接invoke调用加载 集成classLoder 负责加载类型 classLoader 负责 ClassInjector 将一个类,注入到java.lang.ClassLoader。
MultipleParentClassLoader 此{@link java.lang.ClassLoader}能够从多个父级加载类。此类加载程序隐式地将bootstrap定义为其直接父级,因为所有类加载程序都需要它。 当创建继承超级类型和由不同的非兼容类加载器定义的接口的类型时,这很有用。 注意:该类加载器的实例可以多次与其父级具有相同的类加载器,直接或间接地由共享公共父类加载器的多个父级间接实现。根据定义,这意味着引导类加载器是该类加载器的父级的{@code#(直接父级)+1}倍。 对于{@link java.lang.ClassLoader#getResources(java.lang.String)}方法,这意味着该类加载器 通过多次表示同一个类加载器,可能多次返回相同的url。 重要:此类加载器不支持其多个父级的程序包的位置。这打破了 通过直接通过此类加载器加载类(例如通过子类化)或通过使用该类加载器的子类加载器加载类来加载类时的包相等性。ByteArrayClassLoader 一个能够加载显式定义的类的java.lang.ClassLoader。类加载器将释放加载由其二进制数据定义的类,则所有二进制资源。该类加载器是线程安全的,因为类加载机制仅从同步上下文中调用。 注意:该类加载器的实例使用bytebuddy 模式返回其表示的类加载器的URL。 这些URL不表示URI,因为两个具有相同名称的类产生相同的URL,但可能表示不同的字节数组。 注意:任何类和包定义都是使用创建者的AccessControlContext执行的。脚手架,这个是使用asm 的API,来生成字节码的。 bytebuddy中,redfine和rebase。还有subclass。
这里只介绍功能,不介绍细节。太复杂了。
方法
SaveIn 把dynamic类型保存到文件inject 把一个类型注入到jar包中toJar 生成一个jar包。Nexus是用于使用LoadedTypeInitializers 初始化类的全局调度程序 。为此,系统类加载器应以显式方式加载此类。然后,任何检测到的类都将一个代码块注入其静态类型初始化器中,该代码块调用此非常相似的联系,该Nexus已预先注册了已加载的类型初始化器。
注意:Nexus通过将PROPERTYsystem属性设置为,可以完全禁用类的可用性及其向系统类加载器的注入false。
重要提示:绝对不能直接访问nexus,而只能通过NexusAccessor 来确保系统类加载器已加载nexus。否则,如果某个类由另一个在其层次结构中没有系统类加载器的类加载器加载,则该类可能无法初始化自身。
负责加载类并初始化它LoadedTypeInitializer的。
passive 被动的active 积极的Lazy 懒加载Disabled 不加载实施用于确定是否应生成可见性桥的策略。往往调用时,会产生类不可见的问题。 一个brige就是用来充当这个作用的。 不完全是java bridge method,这个能帮助理解桥的作用
ALWAYS 总能看见ON_NON_GENERIC_METHOD 只对非泛型生成桥NEVER 从不核心的方法 只有一个方法 matches 被用来和目标类进行匹配 入门介绍