JVM类加载器

JVM 自带的类加载器只能加载默认 classpath 下的类,如果我们需要加载 应用程序之外的类文件呢?比如网络上的某个类文件,这种情况一般就要使用自 定义加载器了。自定义类加载器可以自己指定类加载路径,可以实现系统在线升级(热替换)等操作。在我们使用的 tomcat 服务器, spring 框架, mybatis 框 架等其内部都自己定义了类加载器。
我们如何自己定义类加载器呢?我们自己写类加载器一般需要直接或间接 继承 ClassLoader 类,然后重写相关方法,具体过程可参考后续小节内容。

3.2.1. 准备工作

在指定包中创建一个自己写的类,例如:

package pkg;
public class Search {
    static {
        System.out.println("search static");
    }
    public Search() {
        System.out.println("search constructor");
    }
}
说明:后续可将此类拷贝到指定目录,由自己指定的类加载器进行加载。

3.2.2. 基于 ClassLoader 创建

代码实现:


class MyClassLoader01 extends ClassLoader {
    private String baseDir;

    public MyClassLoader01(String baseDir) {
        this.baseDir=baseDir;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        byte[] classData = loadClassBytes(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }
    /**自己定义*/
    private byte[] loadClassBytes(String className) {//pkg.Search
        String fileName =baseDir+className.replace('.', File.separatorChar)
                + ".class";
        System.out.println("fileName="+fileName);
        InputStream ins=null;
        try {
            ins= new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }finally {
            if(ins!=null)try{ins.close();}catch(Exception e) {}
        }
    }
}
说明:自己写类加载器一般不建议重写 loadClass 方法,当然不是不可以重写。
定义测试方法:假如使用自定义类加载器加载我们指定的类,要求被加载的类应与当前类不在同一个命名空间范围内,否则可能直接使用 AppClassLoader 进 行类加载。
public class TestMyClassLoader01 {
    public static void main(String[] args) throws Exception{
        String baseDir="F:\\WORKSPACE\\";
        MyClassLoader01 classLoader = new MyClassLoader01(baseDir);
//此类不要和当前类放相同目录结构中
        String pkgCls="pkg.Search";
        Class<?> testClass = classLoader.loadClass(pkgCls);
        Object object = testClass.newInstance();
        System.out.println(object.getClass());
        System.out.println(object.getClass().getClassLoader());
    }
}
输出的类加载名称应该为我们自己定义的类加载器名称。

3.2.3. 基于 URLClassLoader 创建

URLClassLoader 继承 ClassLoader ,可以从指定目录, jar 包,网络中加载指 定的类资源。
代码示例:基于此类加载器可加载网络中的
class MyClassLoader02 extends URLClassLoader {
    public MyClassLoader02(URL[] urls) {
        super(urls,null);// 指定父加载器为 null
    }
}
编写测试类
public class TestMyClassLoader02 {
    public static void main(String[] args)throws Exception {
        File file=new File("f:\\workspace\\");
//File to URI
        URI uri=file.toURI();
        URL[] urls={uri.toURL()};
        ClassLoader classLoader = new MyClassLoader02(urls);
        Class<?> cls = classLoader.loadClass("pkg.Search");
        System.out.println(classLoader);
        Object obj = cls.newInstance();
        System.out.println(obj);
       
    }
}

3.3. 基于类加载器实现热替换

当我们的项目运行时假如需要实现在线升级(也就是常说的热替换),可以通过 自定义类加载实现,例如  自定义类加载器
class MyClassLoader03 extends ClassLoader {
    private String basedir; // 需要该类加载器直接加载的类文件的基目录
    private HashSet<String> loadClasses; // 需要由该类加载器直接加载的类名
    public MyClassLoader03(String basedir, String[] classes)
            throws IOException {
// 指定父类加载器为 null,打破双亲委派原则
        super(null);
        this.basedir = basedir;
        loadClasses = new HashSet<String>();
        customLoadClass(classes);
    }
    // 获取所有文件完整路径及类名,刷入缓存
    private void customLoadClass(String[] classes) throws IOException {
        for (String classStr : classes) {
            loadDirectly(classStr);
            loadClasses.add(classStr);
        }
    }
    // 拼接文件路径及文件名
    private void loadDirectly(String name) throws IOException {
        StringBuilder sb = new StringBuilder(basedir);
        String classname = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator).append(classname);
        File classF = new File(sb.toString());
        instantiateClass(name,new FileInputStream(classF),
                classF.length());
    }
    // 读取并加载类
    private void instantiateClass(String name, InputStream fin, long len)
            throws IOException {
        byte[] raw = new byte[(int) len];
        齐雷 qilei@tedu.cn 1-14
        fin.read(raw);
        fin.close();
        defineClass(name, raw, 0, raw.length);
    }
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class<?> cls;
// 判断是否已加载(在名字空间中寻找指定的类是否已存在)
        cls = findLoadedClass(name);
        if(!this.loadClasses.contains(name) && cls == null)
            cls = getSystemClassLoader().loadClass(name);
        if (cls == null)throw new ClassNotFoundException(name);
        if (resolve)resolveClass(cls);
        return cls;
    }
}
编写测试类
public class TestMyClassLoader03 {
    public static void main(String[] args) throws Exception {
        MyClassLoader03 loader=new MyClassLoader03("f:\\workspace\\",
                new String[] {"pkg.Search"});
        Class<?> cls = loader.loadClass("pkg.Search");
        System.out.println(cls.getClassLoader());
        Object search = cls.newInstance();
        System.out.println(search);
        Thread.sleep(20000);
        loader=new MyClassLoader03("f:\\workspace\\",
                new String[] {"pkg.Search"});
        cls=loader.loadClass("pkg.Search");
        System.out.println(cls.getClassLoader());
        search = cls.newInstance();
        System.out.println(search);
    }
}
说明:程序可运行期间可以将不需要的目标类删除,然后将新的目标类放到原先 的地方,以实现热替换操作。
上一篇:[xboard]ok210-3 S5PV210光盘资料与功能测试


下一篇:C++_ 头指针在链表的操作中用来标识链表的起始位置