自定义类加载器

JVM默认的三个类加载器

AppClasLoader  系统类加载器   

ExtClassLoader  扩展类加载器

BootstrapClassLoader  根类加载器  (由C++实现,在控制台打印出来的是null)

他们加载的jar包所在的路径不同

父委托机制

  1. Invoke findLoadedClass(String) to check if the class has already been loaded.

  2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.

  3. Invoke the findClass(String) method to find the class. 

 可以看出一个类的加载是通过调用类加载器的loadClass方法来完成的,第一步检查是否已经加载过此类,第二步递归调用父加载器加载,第三步调用自己的findClass加载

自定义类加载器,打破了父委托机制

public class MyClassLoader extends ClassLoader {

    private String dir = "C:\Users\Administrator\Desktop";

    public MyClassLoader() {
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/");
        File classFile = new File(dir, classPath + ".class");
        if (!classFile.exists()) {
            throw new ClassNotFoundException("The class " + name + " not found under " + dir);
        }

        byte[] classBytes = loadClassBytes(classFile);
        if (null == classBytes || classBytes.length == 0)
            throw new ClassNotFoundException("load the class " + name + " failed");

        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    private byte[] loadClassBytes(File classFile) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                FileInputStream fis = new FileInputStream(classFile)) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;

        // 如果是以java开头的类,就采用系统类加载器加载
        if (name.startsWith("java.")) {
            try {
                ClassLoader system = ClassLoader.getSystemClassLoader();
                clazz = system.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (Exception e) {
                // ignore
            }
        }
        // 打破了类加载的父委托机制
        // 先自己加载,自己加载不了,就委托父加载加载
        try {
            clazz = findClass(name);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (clazz == null && getParent() != null) {
            getParent().loadClass(name);
        }
        return clazz;
    }

}


public class Test {

    public static void main(String[] args) throws Exception {

        MyClassLoader myClassLoader = new MyClassLoader();
        Class aClass = myClassLoader.loadClass("com.irish.algorithm.MyObject");
        System.out.println(aClass.getClassLoader());
    }
}

类加载器的命名空间

每一个类加载器都有自己的命名空间,由该类加载器及其所有的父加载器类构成

类的运行时包

由加载该类的命名空间+该类的可见包组成

类加载的父委托机制优缺点

优点:防止内存中出现多份同样的字节码 ,保证底层的类不会被用户写的覆盖,保证安全(即使绕过了父委托机制,与jvm底层的同名类也加载不成功)

缺点:子加载器的命名空间对父加载器不可见

热部署是采用类加载器实现的

public class User {

    public void add() {
        System.out.println("User 1.0 版本!");
    }
}
public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
       //默认从当前类的目录下读取文件,传入的字符串以'/'开头的话,就是以classpath为目录 InputStream
is = this.getClass().getResourceAsStream(fileName); byte [] bytes = new byte[is.available()]; is.read(bytes); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { // TODO: handle exception throw new ClassNotFoundException(); } } }
public class Test {

    public static void main(String[] args) throws Exception {
        
        System.out.println("开始加载版本1.0");
        loadUser();
        Thread.sleep(10000);
        //当有类进行修改后,class文件发生了变化,重新加载该类的字节码
        System.out.println("开始加载版本2.0");
        loadUser();
        
    }
    
    
    public  static void loadUser() throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> findClass = classLoader.findClass("com.j0620.User");
        Object obj = findClass.newInstance();
        Method method = findClass.getMethod("add");
        method.invoke(obj);
        System.out.println(obj.getClass());
        System.out.println(obj.getClass().getClassLoader());
    }
}

 项目结构:

原文地址:https://www.cnblogs.com/moris5013/p/10930077.html