前面几节我们分析了dubbo的spi、ioc和aop,这一篇我们探究dubbo的最后一个内核,动态编译
在分析动态编译前,先介绍下javassitst
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
Javassist中最为重要的是ClassPool,CtClass ,CtMethod 以及 CtField这几个类。
ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。
CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。
CtMethods:表示类中的方法。
CtFields :表示类中的字段
下面一段代码会生成一个top.ss007.GenerateClass
ClassPool pool = ClassPool.getDefault();
CtClass ct = pool.makeClass("top.ss007.GenerateClass");//创建类
ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口
try {
CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段
f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
ct.addField(f);//将字段设置到类上
//添加构造函数
CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
ct.addConstructor(constructor);
//添加方法
CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);
ct.writeFile();//将生成的.class文件保存到磁盘
//下面的代码为验证代码
Field[] fields = ct.toClass().getFields();
System.out.println("属性名称:" + fields[0].getName() + " 属性类型:" + fields[0].getType());
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
}
生成的GenerateClass public class GenerateClass implements GenerateInterface { public int id; public GenerateClass(int var1) { this.id = var1; } public void hello(String var1) { System.out.println(var1); } }
javassist同样可以实现aop
假设有这么一个类
public class Point {
private int x;
private int y;
public Point(){}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}
我们要动态的在内存中在move()
方法体的前后插入一些代码
public void modifyMethod()
{
ClassPool pool=ClassPool.getDefault();
try {
CtClass ct=pool.getCtClass("top.ss007.Point");
CtMethod m=ct.getDeclaredMethod("move");
m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");
m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");
ct.writeFile();
//通过反射调用方法,查看结果
Class pc=ct.toClass();
Method move= pc.getMethod("move",new Class[]{int.class,int.class});
Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});
move.invoke(con.newInstance(1,2),1,2);
}
...
}
最终move方法会变成
public void move(int dx, int dy) {
System.out.print("dx:" + dx);System.out.println("dy:" + dy);
this.x += dx;
this.y += dy;
Object localObject = null;//方法返回值
System.out.println(this.x);System.out.println(this.y);
}
关于更多javassist方面的知识,可以参考官网 http://www.javassist.org/ 这里就不细说了
------------------------------------------------------
言归正传,说完了javassist
找到Extensionloader#createAdaptiveExtensionClass()
private Class<?> createAdaptiveExtensionClass() { String code = this.createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
第一句话我们之前已经分析了就是获得一段代码字符串,第二句话是获取类加载器
重点看这句话
Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();
Compiler的实现类中是有@Adaptive注解的一个实现类,就是AdaptiveCompiler,在dubbo的配置文件中信息是这样的
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler
所以最终的compiler是AdaptiveCompiler
进入AdaptiveCompiler
@Adaptive public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public AdaptiveCompiler() { } public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } public Class<?> compile(String code, ClassLoader classLoader) { ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; Compiler compiler; if (name != null && name.length() > 0) { compiler = (Compiler)loader.getExtension(name); } else { compiler = (Compiler)loader.getDefaultExtension(); } return compiler.compile(code, classLoader); } }
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
获取到的loader为
if (name != null && name.length() > 0) { compiler = (Compiler)loader.getExtension(name); } else { compiler = (Compiler)loader.getDefaultExtension(); }
会根据AdaptiveCompiler的DEFAULT_COMPILER是否会空来走,因为之前injectExtension并没有注入DEFAULT_COMPILER的值,所以走下面 compiler = (Compiler)loader.getDefaultExtension();
getDefaultExtension()会根据@SPI的value值来创建一个Extension
return null != this.cachedDefaultName && this.cachedDefaultName.length() != 0 && !"true".equals(this.cachedDefaultName) ? this.getExtension(this.cachedDefaultName) : null;
Compiler上@SPI上的value为@SPI("javassist")
所以compiler为JavassistCompiler
实际上AdaptiveCompiler就是一个装饰类,实际被装饰的对象就是JavassistCompiler或者别的
JavassistCompiler的compiler方法在AbstractCompiler中
public Class<?> compile(String code, ClassLoader classLoader) { code = code.trim(); Matcher matcher = PACKAGE_PATTERN.matcher(code); String pkg; if (matcher.find()) { pkg = matcher.group(1); } else { pkg = ""; } matcher = CLASS_PATTERN.matcher(code); if (!matcher.find()) { throw new IllegalArgumentException("No such class name in " + code); } else { String cls = matcher.group(1); String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls; try { return Class.forName(className, true, ClassHelper.getCallerClassLoader(this.getClass())); } catch (ClassNotFoundException var11) { if (!code.endsWith("}")) { throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n"); } else { try { return this.doCompile(className, code); } catch (RuntimeException var9) { throw var9; } catch (Throwable var10) { throw new IllegalStateException("Failed to compile class, cause: " + var10.getMessage() + ", class: " + className + ", code: \n" + code + "\n, stack: " + ClassUtils.toString(var10)); } } } } }
return Class.forName(className, true, ClassHelper.getCallerClassLoader(this.getClass()));
这句话会根据className去加载这个class,但是由于xxx$Adaptive事先在路径中没有文件,所以会报错
进入catch
doCompile(className, code);
JavassistCompiler#doCompile
比较复杂,归纳一下就是利用Javassist编译出class,具体原理我们已经在一开始说Javassist的时候已经说明了
这样xxxx$Adaptive的类就被动态编译出来了
本篇结束。