学习于:掘金小册 《深度剖析apache dubbo 核心技术内幕》
web:Java动态字节技术之Javassist
看dubbo的时候重新看到了这东西,然后捡起一些东西来写写。
javassist是一个字节码修改工具,不需要了解字节码指令(同类型的ASM需要),性能比ASM稍差些,但是简单。操作起来跟java反射有些类似。
主要有 ClassPool ,CtClass,CtMethod,CtField,,构造器等。
顾名思义,基本可以理解是干啥的,classpool用来存储ctclass,工作方式与 JVM 类装载器非常相似。
概念啥的网上看看就差不多了,我这里也是基本层次的了解,写个demo比较好,写个复制一个类,然后修改它的方法体,然后加个属性,然后修改类名,然后调用方法的demo。
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
这是maven依赖。
public static void main(String[] args) throws Exception{
ClassPool cp = ClassPool.getDefault();
//从池中获取全限定名的ctclass
CtClass ct = cp.get("com.hbr.service.MyWorld");
String name = ct.getName();
System.out.println(name);
String packageName = ct.getPackageName();
System.out.println(packageName);
//复制原来的类结构,创建一个新的类
ct.setName("com.hbr.service.$MyWorld");
//增加foo方法
CtMethod ctMethod = new CtMethod(CtClass.intType,"foo",new CtClass[]{CtClass.intType,CtClass.intType},ct);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("return $1*$2;"); //$1 $2 代替形参
ct.addMethod(ctMethod); //加入新的方法
//增加一个属性
CtField ctField = new CtField(CtClass.intType,"age",ct);
ctField.setModifiers(Modifier.PUBLIC);
ct.addField(ctField,"18");
//写入到idea的maven的target目录下,写在其它目录可能不会加载,idea和maven的配置问题
ct.writeFile(ClassPool.class.getClassLoader().getResource(".").getFile());
//ctclass到class的转换
Class clazz = ct.toClass();
Object instance = clazz.newInstance();
Method fooMethod = clazz.getMethod("foo", int.class, int.class);//这里不能使用integer.class
Object result = fooMethod.invoke(instance, 2, 2);
System.out.println(result);
}
ClassPool cp = ClassPool.getDefault();
Returns the default class pool. The returned object is always identical since this method is a singleton factory.
The default class pool searches the system search path, which usually includes the platform library, extension libraries, and the search path specified by the -classpath option or the CLASSPATH environment variable.
这里注意它的搜索路径
When this method is called for the first time, the default class pool is created with the following code snippet:
ClassPool cp = new ClassPool();
cp.appendSystemPath();
这是它的替代方式
If the default class pool cannot find any class files, try ClassClassPath and LoaderClassPath.
总之就是返回一个包含了默认路径的class的class池。这个理解比较重要。
然后可以通过
cp.insertClassPath("D:codemygitdubbostudyoutyame");
这个方法设定搜索类的路径。(说实话,这个方法有点坑,特别是在maven工程中,跟target目录之类的相关)
String path=ClassPool.class.getClassLoader().getResource(".").getFile();
这个可以获取target下的class路径
CtClass ctClass2 = cp.makeClass("com.hbr.service.MyWorld");
ctClass2.writeFile(path);
这样就可以创建一个包名为com.hbr.service,类名为MyWorld的class。
即
![a49c9bf7984caff8153b55561fa7e612.png](https://i-blog.csdnimg.cn/blog_migrate/0ce20408bf8cf10c5719f61caf45e6eb.png)
这样就是单纯的没有.java文件,直接构造一个class文件,就这样
![fbb8708a9f185105b018a1def2439331.png](https://i-blog.csdnimg.cn/blog_migrate/10ed40607e0a6ea335dc3a660d4798bc.png)
然后回到我们的demo
输入
![382c8082715e3452ddbd1f7e4d427714.png](https://i-blog.csdnimg.cn/blog_migrate/47f7c0e372d81b0c8a784cd84db5071a.png)
输出:
![abf51fc0d4fdd49db71241ed1f029cf9.png](https://i-blog.csdnimg.cn/blog_migrate/3721f6f48964b6ce76eaaa25259fdc11.png)
![4a5fd213b09b3ea12a68442fed24e2fb.png](https://i-blog.csdnimg.cn/blog_migrate/927505c3c120ef350445dfdb84aec08d.png)
结构是
![16ea4a63d2ce1975f925e48d76063ec0.png](https://i-blog.csdnimg.cn/blog_migrate/07c101a7fd14e10d3e8b04d32e7b700f.png)
结果增加了一个foo方法,然后反射调用了这个foo方法,然后我把class写入到了classes目录下。(其实是可以不写的) 大家可以自己尝试着操作里面的api,加深一下印象。
然后回到dubbo这里。dubbo会给每个服务提供者的实现类生产一个wrapper类,它最终调用服务提供方的接口实现类,wrapper类的存在是为了减少反射的调用。
可以查看 dubbo生成实现类的Wrapper类_Venture758的专栏-CSDN博客
重点:public class Wrapper1 extends Wrapper implements ClassGenerator.DC
![86d823ab6fbebf1cd0c63578880ce309.png](https://i-blog.csdnimg.cn/blog_migrate/c73a8c80fde6beea7eb330996aa99c0e.jpeg)
反射传过来类名,方法名,参数类型和参数值。 通过类名我们可以反射获取实例,也就是上面的object,方法名直接是上面的string,参数之类的直接传递即可。
一般来说比如jdk的动态代理就是反射 method.invoke(instance,args)
而这里是invokeMethod直接调用你的这个方法,跟平常的调用没什么区别。 这里手动判断方法名,然后控制要调用的的方法,虽然生成这个类的操作没有反射操作这么简单,但是真正调用的时候,开销就比较小。 而且关键是,生成这个wrapper类的操作虽然比较麻烦,但是它是在dubbo服务启动时生成的,所以不会对运行时带来开销。
然后看看这个wrapper类的生成。
![7657a8ec063db2476db798db63e60029.png](https://i-blog.csdnimg.cn/blog_migrate/92eb57cfb8deff79a88a45099bf1438b.png)
默认有两种方式,一个是JavassistProxyFactory,另一个是JdkProxyFactory。spi默认使用javassist。
看一下源码实现:
![2006b751e5ab282e9e56c3c196f94ca3.png](https://i-blog.csdnimg.cn/blog_migrate/504311223bdfb78ecdb87d5405e92156.jpeg)
跟进去
![9f82b05b28196aace226bf8cafcd63d1.png](https://i-blog.csdnimg.cn/blog_migrate/244a967930c71c0d3847ff5484cd7673.jpeg)
进到ClassGenerator里面就是我们熟悉的javassist的api操作了。
然后反手看jdk那个
![6dd58a8dcb415b785ab348fae7f95c44.png](https://i-blog.csdnimg.cn/blog_migrate/b789f21e95cb254724ca5a481f4c77e1.jpeg)
直接反射调用。没啥好说的。
值得注意的是上面的getproxy中的proxy,jdk的proxy是反射包的,javassist的proxy是dubbo包的,即javasist动态生成Proxy再调用newinstance。
![d019dc4d742bc0b01139770099e83d73.png](https://i-blog.csdnimg.cn/blog_migrate/d27a5e0f0b785169d39dcadae486ebd9.jpeg)
也就是完全替代了动态代理的切面增强功能。。。定制化非常固化,适合框架使用,我们还是使用jdk的动态代理比较好,或者cglib吧。