2022年4月24日 作者 zeroheart

打破类加载双亲委派机制

(26条消息) java编程之自定义类加载器以及打破类加载双亲委派的方法_天码行空码飞舞的博客-CSDN博客_自定义类加载器,如何打破双亲委派

1.如何自定义类加载器

我们就知道关键的方法是findClass,那我们只要继承ClassLoader,重写findClass方法就好了(里面调用了defineClass方法)。

package com.loman.classloader;
 
import com.loman.classloader.Test;
 
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
 
 
public class CustomClassLoader extends ClassLoader {
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("d:/test/", name.replace(".", "/").concat(".class"));
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(f);
            baos = new ByteArrayOutputStream();
            int b = 0;
 
            while ((b=fis.read()) !=0) {
                baos.write(b);
            }
 
            byte[] bytes = baos.toByteArray();            
 
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(baos != null) {
                baos.close();
            }            
            if(fis != null) {
                fis.close();
            }
            
        }
        return super.findClass(name); //throws ClassNotFoundException
    }
 
    
    public static void main(String[] args) throws Exception {
        ClassLoader l = new CustomClassLoader();
        Class clazz = l.loadClass("com.loman.classloader.Test");
        Class clazz1 = l.loadClass("com.loman.classloader.Test");
 
        System.out.println(clazz == clazz1);
 
        Test h = (Test)clazz.newInstance();   //Test是自己定义的一个类,代码就不贴了
        h.method1();    //Test类的实例方法                            
 
        System.out.println(l.getClass().getClassLoader());
        System.out.println(l.getParent());
 
        System.out.println(getSystemClassLoader());
    }
}

2.打破双亲委派

从图中可以看出,双亲委派是一个自底向上逐层检查类是否加载,自顶向下逐层进行实际查找和加载的过程,有点递归的意思。检查的过程就是加载之前,先检查一下是不是已经加载过了,如果自己加载了那就返回结果,如果自己没加载,那就问问父加载器(注意不是类加载器的加载器,也不是类加载器的父类加载器)是否加载了,如果父类加载器加载了就直接返回结果,如果没有就继续往上一层去检查。那如果到了最顶层的类加载器还是没有加载的话,那就“调头”,逐层往下去查找,如果能找到(类加载器有各自的“责任范围”)那就加载,然后返回结果,如果找不到,那就交给下一层,这样层层递推。如果到最底层还是找不到,怎么办?这时会抛出异常:ClassNotFoundException,这个异常相信大家都不陌生,那可能的原因就是.class文件的路径写错了,或者文件不存在。

双亲委派的目的主要是为了安全,其次就是避免重复加载。

既然我们知道了双亲委派的原理流程,那要怎么去打破它呢?通过分析jdk源码,可以得到答案,那就是在自定义类加载器的基础上,重写loadClass方法。这里我就只贴重写的loadClass方法

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
 
    File f = new File("D:/test/" + name.replace(".", "/").concat(".class"));
 
    if(!f.exists()) return super.loadClass(name);
 
    try {
 
        InputStream is = new FileInputStream(f);
 
        byte[] b = new byte[is.available()];
        is.read(b);
        return defineClass(name, b, 0, b.length);
    } catch (IOException e) {
        e.printStackTrace();
    }
 
    return super.loadClass(name);
}

(26条消息) 为什么tomcat要打破类加载概念 – CSDN

1. Tomcat类加载机制要考虑的问题

Tomcat作为Servlet容器,它负责加载我们的Servlet类,此外它还负责加载Servlet所依赖的 JAR 包。并且Tomcat本身也是也是一个Java程序,因此它需要加载自己的类和依赖的JAR包,所以可能要考虑这几个问题:

  1. 假如在Tomcat中运行了两个Web应用程序,两个Web应用中有同名的 Servlet,但是功能不同,Tomcat需要同时加载和管理这两个同名的Servlet 类,保证它们不会冲突,也就是说,Web应用之间的类需要隔离
  2. 假如两个Web应用都依赖同一个第三方的JAR包,比如Spring,那Spring的JAR包被加载到内存后,Tomcat要保证这两个Web应用能够共享,也就是说Spring的JAR包只被加载一次,否则随着依赖的第三方JAR包增多,JVM 的内存会膨胀
  3. Tomcat自身也是一个Java程序,需要隔离Tomcat本身的类和Web应用的类,避免相互影响,比如Web应用中定义了一个同名类导致Tomcat本身的类无法加载

所以,Tomcat是如何来解决这些问题的?答案是通过设计多层次的类加载器。

热加载、osgi等都打破了双亲委派。