这个介绍文档不侧重讲如何用,官网文档有了,而是通过分析bytebuddy的源码,和官网文档给出一个轮廓,让使用者更好的理解。更多的细节也有一篇源码分析。后面细讲吧。
需要了解更多的细节,可以看我的源码解析,我会尽量完善的。
作者写的描述可以看出,最初的目的还是为写agent.jar,但是目标更大,目的是提供一个可以使用简单api来生成和修改类的工具。
这个信息来自于bytebuddy自己的说明
性能对比
类的生成策略面临一个权衡,Byte Buddy的主要重点在于以最少的运行时间生成代码。
这是面对类生成,接口实现等行为的不同增强工具的性能对比,总体看起来bytebuddy的性能没有特殊的地方,没有恶化。
到底怎么一个权衡策略,可以看官网,这不是关注的重点
java proxy
Java类库附带一个代理工具包,该工具包允许创建实现一组给定接口的类。这个内置的代理使用很方便,但功能非常有限。比如代理只能面对一个已经存在的接口,
但是对类进行扩展的时候,proxy办不到
cglib
太早了,没人维护了。
该代码生成库是在最初几年的Java实现,它也不幸的是没有与Java平台的发展跟上。尽管如此,cglib仍然是一个功能非常强大的库,但是它的积极开发却变得相当模糊。因此,它的许多用户都离开了cglib。
javassist
自带了一个相比javac弱小编译器,而且动态生成字节码时容易出错。
该库带有一个编译器,该编译器采用包含
Java
源代码的字符串,这些字符串在应用程序运行时会转换为Java字节代码。因为Java源代码显然是描述Java类的一种很好的方法,所以这是非常雄心勃勃的,并且从原则上讲是个好主意。
但是,
Javassist
编译器在功能上无法与javac编译器相提并论,并且在动态组成字符串以实现更复杂的逻辑时,容易出错。此外,
Javassist
附带了一个代理库,该代理库与JCL的代理实用程序相似,但是允许扩展类,并且不限于接口。
但是,Javassist代理工具的范围在API和功能方面同样受到限制。
首先bytebuddy中定义了一个TypeDescription对每一个类进行封装。
还设计了一个
TypePool
可以来存储这些
TypeDescription
,相当于一个池子。
了解过
instrument
的原理就不陌生,每有一个类加载时会触发一个事件,调用回调函数里
transformers
对类进行加工。
由于
bytebuddy
自己可以读取类的定义,或者提供修改后的定义— 一个
TypeDescription
,放在
TypePool
池子里。
所以
bytebuddy
可以显示的加载类(自己去加载类),从而不触发加载事件,阻止jvm内置的load函数去加载类。
bytebuddy可以主动加载未加载进内存的类,提供类定义,放在TypePool中,提供给JVM使用。
但是更细节的原理,可以参考源码去了解。我这里也没有做深入
默认的加载策略。新建当前
classLoader
的子
classLoader
。使用子classLoader来加载新定义的类。
好处
是被加载的类可以看见所有父classloader的类。
缺点
新的 ClassLoader,意味着新的namespace。这意味着可以加载两个具有相同名称的类,只要这些类由两个不同的类加载器加载即可。这样,即使两个类都代表相同的类实现,Java虚拟机也不会将这两个类视为相等。
这意味着,如果两个类都未使用同一类加载器加载,则该类
example.Foo
将无法访问另一个类的程序包私有方法
example.Bar
。
同样,如果
example.Bar
扩展了
example.Foo
,任何覆盖的包级别私有方法将不起作用,但将委派给原始实现。
利用反射,利用反射调用
classloader
的加载器去直接加载构造好的类型,而不用经过
find & load
过程。
wrpper
和c
hild-first
的好处是,我们加载的类都会记录在
manifest
文件里,这个文件就是生成的
jar
包中,包含的
class
信息。使用
inject
不会在
manifest
生成信息。
有时加载想要获取已经被加载类的字节码,记录在
manifest
中的类可以通过
ClassLoader::getResourceAsStream
读取。但是
inject
是用反射注入的,就没办法取到。
这里的细节或者疑问,没有深入。有兴趣的话可以去看看。
ElemetMatcher接口就只有一个方法
boolean matches(T target)
作用如同所讲的,就是用来锁定需要匹配的目标方法。
我们到底需要对那个类,它的什么方法,什么field,做出该表。ElementMatcher matches 可以判断是否是目标方法。
使用者的开发往往侧重在这里,需要定义自己实现,定义匹配规则。
Junction 接口
就是and 和 or方法,用来处理多Matcher接口
内置的实现
bytebuddy库内置了很多自定义的matcher,使用者可以自己使用。
为method返回一个指定的值。实现了Implementation接口可以被转化为字节码。
fiexdValue
返回一个构造好的对象,一个对象被class文件记住,只有两种方式。
-
被写进classs文件的常量池
。常量池会保留一堆无状态变量。这堆无状态变量就是有方法的name,变量name等信息。除了这些之外还可以存储String变量,和基础变量(未定义,数字,布尔值等)。除了前面说的,还可以保留引用。
-
被保存为类的静态变量
。这样的话,这个值就会被一次性的加载进jvm里,这个会引发一次类的初始化加载。这个初始化加载可以通过
TypeInitializer
源码细致了解。我这里并没有深入。
使用
FixedValue#value(Object)
f,bytebuddy会分析它的参数,优先存在常量池里,其次是
静态
方法,这里没有深入分析,又想去可以看源码。
字节码
class
文件 是一个table结构,常量池是class文件里,保存变量名称,方法名的地方。如下是一个class文件的部分截取
不难看出常量池的内容。这部分被加载时会被一同加载进方法区