Hotcode2中各个Adapter介绍
Hotcode2会在JVM启动阶段和应用运行阶段接入class文件的装载,也就是前一篇文章所说的JVM Init阶段和Runtime阶段。
Adapter按照使用场景也可以分为2类,一种用是在Init阶段,还有一种是用在Runtime阶段。
1 JVM Init阶段
Init阶段的Adapter是在AgentMain.redefineJdkClasses 方法中重新定义各种JVM Class的字节码。
1.1 ClassLoaderAdapter
ClassLoaderAdapter是asm ClassVisitor的子类,
URLClassLoaderAdapter在
java.lang.ClassLoader.defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)
调用的时候,通过asm aop的方式执行2个方法:
-
CRMManager.registerClassLoader(ClassLoader classLoader)
记录当前classLoader已经被加载过,并且为此classLoader关联一个ClassReloaderManager(用于热加载class),并且使用classLoader初始化hotcode的插件。 -
ClassTransformer.transformNewLoadClass(String className, ClassLoader classLoader, byte[] classfileBuffer)
在一个类首次被加载的时候,对其进行转换
1.2 URLClassLoaderAdapter
URLClassLoader的扩展类,当前 URLClassLoader.findClass(final String name)被调用时,替换为调用URLClassLoaderAdapter.findClass(ClassLoader classLoader, String className)。
如果调用URLClassLoader.findResource或者URLClassLoader.findResources,则由相应的FindResourceModifier和FindResourcesModifier调用ClassLoaderHelper.findResource/findResources方法来完成的功能
1.3 JdkMethodAdapter
JdkMethodAdapter.visitMethod
if (name.equals("invoke") && desc.equals("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;")) {
return new NewInvokeModifier(mv, access, name, desc);
}
NewInvokeModifier这个类是在 java.lang.reflect.Method.invoke 方法体中加入AOP逻辑,
loadThis();
invokeStatic(Type.getType(JdkMethodReflectHelper.class), new Method("isNewMethodAccess",
"(Ljava/lang/reflect/Method;)Z"));
Label old = newLabel();
ifZCmp(EQ, old);//判断是否为true
loadThis();
loadArg(0);
loadArg(1);
invokeStatic(Type.getType(JdkMethodReflectHelper.class), new Method("newMethodInvoke",
"(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"));
// unbox(Type.getReturnType(desc));
returnValue();//调用完成后直接返回,不会计息method.invoke调用。一般是热加载后,类中方法有修改,则调用此逻辑
mark(old);//继续method.invoke逻辑
如果JdkMethodReflectHelper.isNewMethodAccess返回true,则调用JdkMethodReflectHelper.newMethodInvoke,否则继续method.inoke的逻辑。转义为java代码为:
java.lang.reflect.Method.invoke方法体代码块:
if(JdkMethodReflectHelper.isNewMethodAccess...){
return JdkMethodReflectHelper.newMethodInvoke...
}
return method.invoke代码调用
1.4 JdkClassAdapter
这个Adapter会对java.lang.Class做很重要的字节码修改,涉及到通过反射获取类信息的方法接口:反射访问当前Class拥有的公私有字段、公私有构造方法、泛型、公私有方法。
这些新增的asm代码主要起以下作用:
- 1、返回值过滤:例如Class.getDeclaredFields的返回值被GetDeclaredFieldsModifier中的asm代码使用JdkFieldReflectHelper.filterHotCodeField将HotCode2新增的字段过滤掉。
再例如:Class.getDeclaredMethods和Class.getMethods的返回值被GetDeclaredMethodsModifier注入代码过滤。
GetDeclaredMethodsModifier:
if (opcode == Opcodes.ARETURN) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(JdkMethodReflectHelper.class),
"filterHotCodeMethods", "([Ljava/lang/reflect/Method;)[Ljava/lang/reflect/Method;");
}
- 2、native方法体重写,目的是将原生的实现替换为Hotcode2的逻辑。例如:Class.privateGetDeclaredMethods方法体中调用Class.getDeclaredMethods0--这个是native方法,在PrivateGetDeclaredMethodsModifier中通过visitMethodInsn将Class.getDeclaredMethods0的方法体流程替换为:
CRMManger.isHotCodeTransform4ReloadClass
if(返回true)
JdkMethodReflectHelper.getDeclaredMethods0
else
Class.getDeclaredMethods0
同样类似的处理方式还有Class.initAnnotationsIfNecessary方法。
采用这种方式的原因:对于jdk native方法是没有方法同字节码的,所以ASM没有办法在JdkClassAdapter中直接在visitMethod中对Class.getDeclaredMethods0进行转换,而是在调用的native方法指令触发的时候,visitMethodInsn方法来接收触发事件,进行native方法体重写。
1.5 JdkFieldAdapter
对java.lang.reflect.Field的方法进行代码扩展处理。主要用途在于判断当前的field是否是修改后的class新增/删除,如果是新增的,则需要重新加载此field所在的class文件。
同样还对访问标注在当前field的Annotation的方法Field.declaredAnnotations进行扩展,增加对修改后的class的field上的Annotation变化的支持。
1.6 JdkConstructorAdapter
java.lang.reflect.Constructor的处理类,在Constructor.newInstance方法和Annotation两方面进行扩展。
构造方法也是方法,同样会遇到class修改后新增/删除构造方法的情况,JdkConstructorAdapter增强逻辑在于重新加载新的class方法,如果调用的是新增的构造方法,则调用Hotcode2中为这个新方法提供的映射接口;调用被删除的构造方法,则抛出HotCodeException;余下的按照正常的流程。
//参考实现类NewInstanceModifier
//转译为java代码如下
JdkReflectHelper.checkReload(this.clazz);
if (JdkConstructorReflectHelper.isNewConstructorAccess(this)) {
return JdkConstructorReflectHelper.newConstructorNewInstance(this, paramVarArgs);
}
//继续java.lang.reflect.Constructor.newInstance代码流程
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
......
return (T) ca.newInstance(initargs);
对于Annotation的处理,主要将新增的Annotation映射到当前class在Hotcode2 runtime对应的shadowClass中。
列位,这里出现了一个新的名词shadowClass,这个词涉及到Hotcode2如何维护jdk Class的实例对象,如何响应java application的方法调用、字段访问。详细信息请参考《Hotcode2原理》(待写O(∩_∩)O~)
1.7 其余Adapter
ResourceBundleControlAdapter/
PropertyResourceBundleAdapter 这2个主要是对properties资源文件的处理。
2 Runtime阶段
用在Runtime阶段的Adapter主要是用于处理应用的class文件。 com.taobao.hotcode2.code.ClassTransformer.transformReloadClass/transformNewLoadClass/transformJdkDynamicProxyClass/transformAnnotationProxyClass 这4个方法通过各种Adapter对应用class文件进行处理。比较重要的:
- AddFieldsHolderAdapter 会对目标类新增2个FieldsHolder类型的变量。
- AddMethodRouterAdapter 会对目标类新增4个路由方法,
public Object __hotcode_instance_method_router__(int paramInt, Object[] paramArrayOfObject);
public Object __hotcode_package_instance_method_router__包名(int paramInt, Object[] paramArrayOfObject);
public Object __hotcode_private_instance_method_router__包名$类名(int paramInt, Object[] paramArrayOfObject);
public static Object __hotcode_static_method_router__包名$类名(int paramInt, Object[] paramArrayOfObject)
- 用于生成辅助类:"类名+$$S$+序号"中方法的ClearMethodBodyAdapter,这个Adapter不会生成方法体
- MethodBodyTransformAdapter,将例如invoker.run() -->greet.hello(),实际上greet.hello()的调用被替换为greet.__hotcode_instance_method_router__(int,Object[])
其余还有些,每个Adapter代码比较少,容易理解。
- JdkDynamicProxyAdapter
- AnnotationProxyAdapter
- StaticFieldDefaultValueAdapter
- BeforeMethodCheckAdapter
- AddClassReloaderAdapter
- NonGeneratedClassMethodBodyTransformerAdapter
- FieldTransformAdapter
- AddInheritedMethodAdapter
- AnonymousInnerClassAdapter
3 总结
如果Spring是面向Bean的编程,iBatis是基于Statement,那么Hotcode2则是由各个Adapter串联起来的!
4 后记
差不多用了一个月多,才断断续续将这3篇文章,其中不停的查资料、骚扰@千臂(很感谢,O(∩_∩)O~),发现自己了解的越多,还需要继续了解的知识点就更多。。。总之,学习是一种信仰!