一、为什么会有这种机制?

        类加载器将.class类加载到内存中时,为了避免重复加载(确保Class对象的唯一性)以及JVM的安全性,需要使用某一种方式来实现只加载一次,加载过就不能被修改或再次加载。

二、什么是双亲委派机制?

(1)当加载一个类时,先判断此类是否已经被加载,如果类已经被加载则返回;

(2)如果类没有被加载,则先委托父类加载(父类加载时会判断该类有没有被自己加载过),如果父类加载过则返回;如果没被加载过则继续向上委托;

(3)如果一直委托都无法加载,子类加载器才会尝试自己加载。

 注:jre/lib包下的jar在JVM启动时就已经被加载到虚拟机中了,当外部定义的[包路径+类名]和jre/lib包下的jar中类一样时,由于父加载器检测此类名已经被加载,所以会拒绝加载。

三、如何打破双亲委派机制?

(一)为什么要打破双亲委派机制?

        有时我们需要多次加载同名目录下的类,比如:当我们在Tomcat上部署多个服务时,不同服务上可能依赖了不同版本的第三方jar,如果此时使用双亲委派机制加载类,会导致多个服务中第三方jar只加载一次,其他服务中的其他版本jar将不会生效,导致请求结果异常。为了避免这种情况,我们需要打破双亲委派机制,不再让父类[应用类加载器]加载,而是为每个服务创建自己的子类加载器。

(二)如何打破双亲委派机制?

        打破双亲委派有两种方式:(1)不委派【SPI机制】;(2)向下委派。

        Tomcat使用父类加载器加载了公用的jar,对于非公用的jar则使用自己的子类加载器进行单独加载。打破双亲委派需要重写findLoadedClass()方法。

 

四、双亲委派示例

package com.wzfx.load;

import java.io.*;
import java.lang.reflect.Method;

/**
 * @author wzfx
 * @description TO DO
 * @date 2023/6/2 18:42
 */
public class MyClassLoader extends ClassLoader {
    // 父加载器(此处不变,此处需要传递当前类的类加载器AppClassLoader)
    private final ClassLoader parent;

    private MyClassLoader(ClassLoader parent) {
        this.parent = parent;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 先检查类是否已经被加载(这一点不变),避免重复加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                //【变更】不让父类先加载,而是自己加载
                c = findClass(name);
            }
            return c;
        }
    }

    /**
     * 重写此方法,加载自定义的那些类
     *
     * @param name 此处传递的name示例:com.wzfx.load.UserTest
     * @return
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 不是自己定义的类依旧按照原来的逻辑走【双亲委派类加载机制】
        if (!name.endsWith("UserTest")) {
            return parent.loadClass(name);
        }
        String codePath = "D:\\Code\\springMybatisTest01\\target\\test-classes\\";
        // 最终是class文件的整体路径
        codePath = codePath + name.replace(".", File.separator) + ".class";
        BufferedInputStream bis = null;
        ByteArrayOutputStream bos = null;
        byte[] bytes = new byte[1024];
        int line = 0;
        try {
            //读取编译后的文件
            bis = new BufferedInputStream(new FileInputStream(codePath));
            bos = new ByteArrayOutputStream();
            while ((line = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, line);
            }
            bos.flush();
            bytes = bos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return defineClass(null, bytes, 0, bytes.length);
    }

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader(MyClassLoader.class.getClassLoader());
        Class<?> aClass = myClassLoader.loadClass("com.wzfx.load.UserTest");
        System.out.println("测试字节码是由" + aClass.getClassLoader().getClass().getName() + "加载的。。");

        //利用反射实例化对象,和调用TwoNum类里面的twoNum方法
        Object o = aClass.newInstance();
        Method twoNum = aClass.getDeclaredMethod("sum", Integer.class, Integer.class);
        Object invoke = twoNum.invoke(o, 10, 23);
        System.out.println("反射并执行方法sum(): " + invoke);
    }

}


Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐