类的热编译+热加载的功能

类加载机制实践

系统调用一个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 Map files = 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(List files,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

你可能感兴趣的:(类的热编译+热加载的功能)