本篇文章我们讨论一下Instrumentation如何实现热加载以及使用过程中需要注意的一些地方。
Instrumentation基础
关于instrumentation的基础知识 。可以阅读此篇文章。小编也是通过阅读此篇文章,加深对instrumentation理解的。
在实际项目中的应用及其需要注意的问题
那instrumentation怎么在热加载中使用呢?又有哪些注意的地方呢?接下来我们一一去说这些问题。
项目中的实际应用
public final class PremainReload {
private static Instrumentation inst = null;
public PremainReload () {
}
public static void premain(String premainArgs, Instrumentation i) {
inst = i;
}
public staitic void reload(Class<?> cls, File file) throws Exception {
byte[] code = loadBytes(file);
ClassDefinition def = new ClassDefinition(cls, code);
inst.redefineClasses(new ClassDefinition[]{def});
}
private static byte[] loadBytes(File file) throws Exception {
byte[] buffer = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read(buffer);
return buffer;
}
}
上面的代码写的比较简单,思路就 是缓存instrumentation实例,然后通过调用reload方法加载要热更新的class文件。
再说一下MANIFEST.MF的设置内容
// 是否能够重新定义类(必须手动设置,否则报错,因为默认为false)
Can-Redefine-Classes: true
// 是否能够重新定义Class文件的转换器(只是更代码可以不用)
Can-Retransform-Classes: true
// premian所在类
Premain-Class: com.pch.reload.PremainReload
例如:我们把包含PremainReload类的打到一个reload.jar中,这样我们在项目启动时加入javaagent即可
java .... -cp -javaagent:./reload.jar ... Main
最后,我们需要建立一个文件夹存放需要热加载的class文件,通过读取里面的文件进行热加载
public class ReloadManager {
public static boolean reloadClass() {
String reloadPath = "D:/pch/reload/";
File dir = new File(reloadPath );
File[] files = dir.listFiles((f) -> f.getName().endsWith(".class"));
for(File f : files) {
FileInputStream in= new FileInputStream(f);
// 使用了asm直接解析类的完整路径
ClassReader cr = new ClassReader(in);
in.close();
Class cls = Class.forName(cr.getClassName().replaceAll("/", "."));
PremainReload.reload(cls, f);
}
}
}
需要注意的地方
通过阅读上面代码我们知道Instrumentation通过调用redefineClasses热加载代码,那redefineClasses有哪些限制呢?
下面摘自redefineClasses注释
/**
* Redefine the supplied set of classes using the supplied class files.
*
* <P>
* 此方法用于在不引用现有类文件字节的情况下替换类的定义,就像从源代码重新编译以进行修复并继续调试时所做的那样。
* 将转换现有类文件字节的位置(例如在字节码检测中)
* This method is used to replace the definition of a class without reference
* to the existing class file bytes, as one might do when recompiling from source
* for fix-and-continue debugging.
* Where the existing class file bytes are to be transformed (for
* example in bytecode instrumentation)
* {@link #retransformClasses retransformClasses}
* should be used.
*
* <P>
* 此方法对集合进行操作,以便允许同时对多个类进行相互依赖的更改(重新定义a类可能需要重新定义B类)
* This method operates on
* a set in order to allow interdependent changes to more than one class at the same time
* (a redefinition of class A can require a redefinition of class B).
*
* <P>
* 如果重新定义的方法具有活动堆栈帧,则这些活动帧将继续运行原始方法的字节码。他重新定义的方法将用于新的调用。
* If a redefined method has active stack frames, those active frames continue to
* run the bytecodes of the original method.
* The redefined method will be used on new invokes.
*
* <P>
* 此方法不会导致任何初始化,除非是在常规JVM语义下发生的初始化。换句话说,重新定义一个类不会导致它的初始值设定项被运行。
* 静态变量的值将保持调用之前的值。
* This method does not cause any initialization except that which would occur
* under the customary JVM semantics. In other words, redefining a class
* does not cause its initializers to be run. The values of static variables
* will remain as they were prior to the call.
*
* <P>
* 重新定义的类实例不受影响
* Instances of the redefined class are not affected.
*
* <P>
* 重新定义可能改变方法体、常量池和属性(除非明确禁止的)
* The redefinition may change method bodies, the constant pool and attributes
* (unless explicitly prohibited).
* 重新定义不能增加,删除、重命名字段或者方法,改变方法签名或者更改继承
* The redefinition must not add, remove or rename fields or methods, change the
* signatures of methods, or change inheritance.
* 重定义不能改变NestHost或NestMembers属性。
* The redefinition must not change the <code>NestHost</code> or
* <code>NestMembers</code> attributes.
* 这些限制将来的版本可能会取消
* These restrictions may be lifted in future versions.
* 在应用转换之前,不会检查、验证和安装类文件字节,如果结果字节出错,此方法将引发异常。
* The class file bytes are not checked, verified and installed
* until after the transformations have been applied, if the resultant bytes are in
* error this method will throw an exception.
*
* <P>
* If this method throws an exception, no classes have been redefined.
* <P>
* This method is intended for use in instrumentation, as described in the
* {@linkplain Instrumentation class specification}.
*
* @param definitions array of classes to redefine with corresponding definitions;
* a zero-length array is allowed, in this case, this method does nothing
* @throws java.lang.instrument.UnmodifiableClassException if a specified class cannot be modified
* ({@link #isModifiableClass} would return <code>false</code>)
* @throws java.lang.UnsupportedOperationException if the current configuration of the JVM does not allow
* redefinition ({@link #isRedefineClassesSupported} is false) or the redefinition attempted
* to make unsupported changes
* @throws java.lang.ClassFormatError if the data did not contain a valid class
* @throws java.lang.NoClassDefFoundError if the name in the class file is not equal to the name of the class
* @throws java.lang.UnsupportedClassVersionError if the class file version numbers are not supported
* @throws java.lang.ClassCircularityError if the new classes contain a circularity
* @throws java.lang.LinkageError if a linkage error occurs
* @throws java.lang.NullPointerException if the supplied definitions array or any of its components
* is <code>null</code>
* @throws java.lang.ClassNotFoundException Can never be thrown (present for compatibility reasons only)
*
* @see #isRedefineClassesSupported
* @see #addTransformer
* @see java.lang.instrument.ClassFileTransformer
*/
void redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException, UnmodifiableClassException;
通过阅读方法的注释,我们知道了这种热加载的限制,使用的时候一定要注意。
在做热加载的时候我们会把编译好的class文件放到指定目录下,然后,调用ReloadManager.reloadClass()即可。我们可以通过发送http请求,执行此方法;也可写一个定时任务不断进行检查是否有需要热更的文件,当有热更文件时执行此方法。
好,Instrumentation实现热加载就说到这里,说的不对的地方欢迎指正。在热加载的最后,我们用ASM获取类的绝对路径,下篇文章介绍一下ASM。