本文共 3908 字,大约阅读时间需要 13 分钟。
系统调用一个class,比如xx.class去查看xx类的信息的时候:
1,loadClass,先在loadedClass里找,找不到,就交给父类去load试试,如果父类还是找不到(返回null),就去调用findClass试试 2,findClass去找未加载进内存的.class方法,如果找到了,就做defineClass的步骤,把它加载进内存 3,defineClass本身是一个native方法,在这个native方法里,当解析到方法的父类的时候(每个方法都有父类,要么是自定义的,要么是Object),会回调当前classLoader的loadClass方法,然后loadClass方法会重复1-3步骤,寻找到父类以后,把父类交出去。按照我的理解,defineClass里所做的把.class文件转化成loadedClass的步骤,就包括了类加载生命周期里的加载,验证,解析步骤
其中findClass里去寻找.class文件(或者其他的二进制流的方法)属于加载的第一步,“1,通过一个类的全限定名来获取定义此类的二进制流。 ”
defineClass这个native属于加载的第二步“2,把字节流所代表的静态存储结构转化为方法区的运行时数据结构”到这里都属于类加载中的加载阶段。
在这个过程中,defineClass回调当前类的loadClass方法来寻找被加载类的父类,这个过程是验证and解析的过程,属于类加载中的连接(验证,准备,解析)阶段。这里找到父类以及其他相关的各种引用的类之后,就已经把符号引用转化为直接引用了,所以到这里递归完以后,连接阶段就结束了
也就是说,在我们看来,调用defineClass的时候,等于把类给初始化了,类的加载,连接(验证,准备,解析)阶段+初始化等,都执行了。
绕回来,代码
这里做了一个类的热编译+热加载的功能这里分两种情况,
1,代码是main方法运行的情况下(非web应用),默认的类加载器是appClassLoader,这种情况下,我们写的.java文件会被编译成.class文件,去进行加载(可以直观的看见和替换.class文件) 2,代码是web应用运行的情况下(以spring为例),默认的类加载器是WebAppClassLoader,这里的.java文件,在编译成.class文件之后,会再被编译成.jar文件,放置在web目录下的web-inf/lib里(不能直观的看见和替换.class文件)但是不影响我们加载类的代码书写
流程大致如下:1,得到文件转换来的二进制流
2,把二进制流给自定义的类加载器
3,调用自定义类加载器的loadClass方法,输入对应名字,
4,复写loadClass方法,做出判断,if name == 我要加载的类, 拿出对应的二进制流,defineClass
else 交给对应的加载器去加载。因为defineClass会回调loadClass方法,所以一定要复写loadClass方法,不然很容易就会报错,比如找不到需要被加载的父类之类的。(虽然报错了也不影响实际使用)
public class HowswapClassLoader extends ClassLoader { private Mapfiles = new HashMap<>(); public HowswapClassLoader(Map files) { this.files.putAll(files); } @Override protected Class findClass(String name) throws ClassNotFoundException { byte[] buf = getByte(files.get(name)); if (buf == null) { return HowswapClassLoader.class.getClassLoader().loadClass(name); } files.remove(name); return defineClass(name, buf, 0, buf.length); } @Override public Class loadClass(String name) throws ClassNotFoundException { return findClass(name); } }
rootPath是临时文件夹就行了,用来存放编译后的.class文件,
比如 System.getProperty(“user.dir”) + “\” + “temp\”;
public class XX{ private static void compilerJavaFiles(Listfiles,String rootPath) { JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); Iterable options = buildOptions(rootPath); StandardJavaFileManager fileManager = javaCompiler .getStandardFileManager(null, null, null); Iterable compilationUnits = fileManager .getJavaFileObjectsFromFiles(files); javaCompiler.getTask(null, fileManager, null, options, null, compilationUnits).call(); } private static List buildOptions(String rootPath) { List options = new ArrayList<>(); options.add("-encoding"); options.add("UTF-8"); //鉴于很多编译文件都需要引用到一些二方三方包,(非java.xx的包),这里通过直接找urls的方式,一次性取出所有编译时候可能用到的classpath,供使用。 options.add("-classpath"); options.add(getClasspath()); //编译目录是root目录,-d表示在根目录下创建(如果没有)package对应的文件夹,然后把.class文件放到对应的package文件夹下 options.add("-d"); options.add(rootPath); return options; } private static String getClasspath() { StringBuilder sb = new StringBuilder(); URL[] urls = ((URLClassLoader)XX.class.getClassLoader()).getURLs(); int length = urls.length; for (int i = 0; i < length; ++i) { URL url = urls[i]; String p = url.getFile(); sb.append(p).append(File.pathSeparator); } return sb.toString(); } }
类的热编译+热加载的功能
1,找到类.java的存放点, 2,读取类.java文件,通过XX.compilerJavaFiles()方法,得到编译后的.class文件 3,把.class文件给HowswapClassLoader去加载进内存 4,通过HowswapClassLoader去获取类(Class转载地址:http://jtalf.baihongyu.com/