zoukankan      html  css  js  c++  java
  • 类加载机制

    一、类加载过程

    类加载器ClassLoader就是加载其他类的类,它负责将字节码文件加载到内存,创建Class对象。一个类可以有多个实例,但是只有一个对应的Class对象。

     ClassLoader加载机制

    类加载器不止有一个,一般程序运行时,会有三个ClassLoader,分别是:

    • 启动类加载器(Bootstrap ClassLoader)—— 由Java虚拟机创建,负责加载Java的基础类,主要是<JAVA_HOME>/lib/rt.jar;
    • 扩展类加载器(Extension ClassLoader)—— 对应sun.misc.Launcher$ExtClassLoader,负责加载Java的一些扩展类,一般是<JAVA_HOME>/lib/ext;
    • 应用类加载器(Application ClassLoader)—— 对应sun/misc.Launcher$AppClassLoader,负责加载应用程序的类。

    这三个类加载器虽然不是父子继承关系,但是存在父子委派关系,即子ClassLoader委托父ClassLoader优先加载,加载失败后再自己尝试加载。比如AppClassLoader加载类时会优先通过parent变量指向的ExtClassLoader加载,ExtClassLoader又委派Bootstrap ClassLoader加载。这种“双亲委派”机制能避免Java类库被覆盖的问题。

    ClassLoader主要通过loadClass方法加载类:

    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     */
        protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException
        {
            // 首先,检查是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        //如果父类不为空,交给父类去加载
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
    
                if (c == null) {
                    //如果依然找不到,调用findClass方法去加载,自定义ClassLoader一般需要重写此方法
                    c = findClass(name);
                }
            }
            return c;
        }
    }

    二、自定义ClassLoader

    Java类加载机制的强大之处在于,我们可以自定义ClassLoader,也就是按照自己的逻辑寻找.class字节码文件,并生成Class对象。这在发挥Java语言的动态性上起着非常重要的作用。

    通过上面的例子知道,ClassLoader的最主要方法是loadClass(),loadClass方法找不到类后最后会调用findClass(name),这是自定义ClassLoader需要重写的主要方法,可以仿照如下方式重写:

        public class MyClassLoader extends ClassLoader {
    
            private static final String BASE_DIR = "data/c87/";
    
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                String fileName = name.replaceAll("\.", "/");
                fileName = BASE_DIR + fileName + ".class";  //得到字节码文件完整路径
                try {
                    /**
                     * 首先将.class文件转化为二进制字节流
                     */
                    FileInputStream is = new FileInputStream(fileName);
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    int len = 0;
                    try {
                        while ((len = is.read()) != -1) {
                            bos.write(len);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    byte[] bytes = bos.toByteArray();
                    is.close();
                    bos.close();
    
                    //通过defineClass方法将二进制字节流转化为Class对象
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException ex) {
                    throw new ClassNotFoundException("failed to load class " + name, ex);
                }
            }
        }

    其中defineClass方法由本地native方法完成,不需要自己写。自定义ClassLoader后,有两大好处:

    • 加载时机更加灵活,我们可以按照自己的逻辑在任意时刻重新加载Class对象。
    • .class文件存储位置更加灵活,不光可以存储于类目录中,也可以存储于网络或数据库等位置。

    三、热部署

    自定义ClassLoader的一个重要应用场景:热部署。所谓热部署,就是在不重启应用的情况下,当类的定义即字节码文件被修改后,能够替换该Class创建的对象。

    主要流程是:

    1. 创建自定义ClassLoader
    2. 加载指定目录下目标类的.class字节码文件
    3. 运用反射创建目标类实例
    4. 监听.class字节码文件变化
    5. .class字节码文件被修改后,重新加载并创建实例

    首先,定义目标类:我们定义一个接口IHelloService,并定义其实现类HelloImpl,HelloImpl即我们的目标类,编译后得到字节码文件HelloImpl.class

        public interface IHelloService {
            public void sayHello();
        }
    
        public class HelloImpl implements IHelloService {
            public void sayHello() {
                System.out.println("hello");
            }
        }

    然后,仿照上面自定义ClassLoader,加载HelloImpl.class字节码文件,并运用反射生成HelloImpl实例:

        //单例,获取HelloImpl实例
        public static IHelloService getHelloService() {
            if (helloService != null) {
                return helloService;
            }
            synchronized (HotDeployDemo.class) {
                if (helloService == null) {
                    helloService = createHelloService();
                }
                return helloService;
            }
        }
    
        private static IHelloService createHelloService() {
            try {
                MyClassLoader cl = new MyClassLoader();  //自定义ClassLoader
                Class<?> cls = cl.loadClass(CLASS_NAME);  //最终调用findClass和defineClass方法加载我们编译好的HelloImpl.class字节码文件,生成HelloImpl类
                if (cls != null) {
                    return (IHelloService) cls.newInstance();  //反射创建HelloImpl实例
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

    调用HelloImpl实例:

        public static void client() {
            Thread t = new Thread() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            IHelloService helloService = getHelloService();
                            helloService.sayHello();
                            Thread.sleep(1000);  //为方便验证,每隔1s调用一次
                        }
                    } catch (InterruptedException e) {
                    }
                }
            };
            t.start();
        }

    此时,输出如下:

    hello
    hello
    hello

    监听.class字节码文件变化,如果监听到HelloImpl.class文件发生变化,则重新创建HelloImpl实例:

        //这里以线程轮寻方式监听.class文件修改日期变化
        public static void monitor() {
            Thread t = new Thread() {
                private long lastModified = new File(FILE_NAME).lastModified();
    
                @Override
                public void run() {
                    try {
                        while (true) {
                            Thread.sleep(100);
                            long now = new File(FILE_NAME).lastModified();
                            if (now != lastModified) {
                                lastModified = now;
                                //如果监听到HelloImpl.class文件发生变化,则重新创建HelloImpl实例
                                reloadHelloService();
                            }
                        }
                    } catch (InterruptedException e) {
                    }
                }
            };
            t.start();
        }
    
        private static void reloadHelloService() {
            helloService = createHelloService();
        }

    我们修改HelloImpl源码,并将重新编译的class字节码文件覆盖原文件:

       public class HelloImpl implements IHelloService {
            public void sayHello() {
                System.out.println("hello,world");  //修改原文件逻辑
            }
        }

    此时,不用重启应用,发现输出已经发生了变化:

    hello
    hello
    hello
    hello,world
    hello,world

    至此,我们已经完成了一次热部署。

  • 相关阅读:
    TransmitFile
    xml
    鼠标划过表格行变色-简洁实现
    关于表变量
    显式接口成员实现
    华为致新员工书
    C#实现的堆栈
    Gridview中合并单元格,某字段的内容相同时如何只显示一个,屏蔽相同列或行的内容(转)
    ASP.NET 验证控件
    动态SQL EXEC
  • 原文地址:https://www.cnblogs.com/not2/p/11170257.html
Copyright © 2011-2022 走看看