双亲委派机制
    在加载类的时候,会一级一级向上委托,判断是否已经加载,从自定义类加载器 -》应用类加载器 -》扩展类加载器 -》启动类加载器,如果到最后都没有加载这个类,则回去加载自己的类。

    image.png

    双亲委托有个弊端:

    不能向下委派,不能不委派

    怎么打破双亲委派机制:(也就是能向下委派和不委派)

    自定义类加载器(不委派)

    spi 机制(向下委派)

    打破双亲委派
    打破双亲委派的两种方式:

    1. 通过 spi 机制,使用 ServiceLoader.load 去加载

    2. 通过自定义类加载器,继承 classloader,重写 loadclass 方法

    SPI 机制
    spi 机制是一种服务发现机制。它通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在 JDBC 中就使用到了 SPI 机制。

    为什么通过 spi 机制就能打破双亲委托?

    因为在某些情况下父类加载器需要委托子类加载器去加载 class 文件。受到加载范围的限制,父类加载器无法加载到需要的文件。

    以 Driver 接口为例,DriverManager 通过 Bootstrap ClassLoader 加载进来的,而 com.mysql.jdbc.Driver 是通过 Application ClassLoader 加载进来的。由于双亲委派模型,父加载器是拿不到通过子加载器加载的类的。这个时候就需要启动类加载器来委托子类来加载 Driver 实现,从而破坏了双亲委派。

    SPI 机制 demo:

    1. public interface HelloService {
    2. public String getName();
    3. }
    4. public class Hello1 implements HelloService{
    5. @Override
    6. public String getName() {
    7. return "hello1";
    8. }
    9. }
    10. public class Hello2 implements HelloService{
    11. @Override
    12. public String getName() {
    13. return "hello2";
    14. }
    15. }
    16. 来一个 main 方法去加载它,将使用 ServiceLoader 来进行加载
    17. public class SPITest {
    18. public static void main(String[] args) {
    19. ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
    20. for (HelloService helloService :serviceLoader){
    21. System.out.println(helloService.getName());
    22. }
    23. }
    24. }

    配置文件,文件名为接口的全路径,文件内容为实现类的全路径,如我的为:com.chuan.service.Hello1

    输出结果:hello1

    只配置了 Hello1,所以只发现了这一个实现类。

    自定义类加载器
    实现逻辑:自定义类继承 classLoader,作为自定义类加载器,重写 loadClass 方法,不让它执行双亲委派逻辑,从而打破双亲委派。

    先看一个没有重写的 demo

    结果:

    sun.misc.Launcher$AppClassLoader@58644d46

    发现是 app 类加载器。

    然后重写 loadClass 方法

    1. public class MyClassLoader extends ClassLoader{
    2. public static void main(String[] args) throws ClassNotFoundException {
    3. // ServiceLoader.load()
    4. MyClassLoader myClassLoader = new MyClassLoader();
    5. Class<?> aClass = myClassLoader.loadClass(Test1.class.getName());
    6. System.out.println(aClass.getClassLoader());
    7. }
    8. protected Class<?> findClass(String className) throws ClassNotFoundException {
    9. System.out.println("My findClass!");
    10. return null;
    11. }
    12. protected Class<?> loadClass(String name, boolean resolve)
    13. throws ClassNotFoundException
    14. {
    15. synchronized (getClassLoadingLock(name)) {
    16. Class<?> c = findLoadedClass(name);
    17. if (c == null) {
    18. long t0 = System.nanoTime();
    19. try {
    20. //修改classloader的原双亲委派逻辑,从而打破双亲委派
    21. if (name.startsWith("com.chuan")){
    22. c=findClass(name);
    23. }
    24. else {
    25. c=this.getParent().loadClass(name);
    26. }
    27. } catch (ClassNotFoundException e) {
    28. }
    29. if (c == null) {
    30. long t1 = System.nanoTime();
    31. c = findClass(name);
    32. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    33. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    34. sun.misc.PerfCounter.getFindClasses().increment();
    35. }
    36. }
    37. if (resolve) {
    38. resolveClass(c);
    39. }
    40. return c;
    41. }
    42. }
    43. }
    44. class Test1{
    45. }

    运行报错,因为没有委派给 app 类加载器,所以找不到加载不了这个类。

    那么新的问题又来了,如果我自定义类记载器和核心类重名怎么办,该怎么加载,又或者我想篡改核心类内容,jvm 又是怎么解决的?

    jvm 肯定解决了这个问题,openjdk 源码在 AccessController.doPrivileged

    学名叫做沙箱安全机制,主要作用是:保护核心类,防止打破双亲委派机制,防篡改,如果重名的话就报异常,这里的重名指包名加类名都重复。

    demo:

    1. package java.lang;
    2. public class Integer {
    3. public static void main(String[] args) {
    4. System.out.println("1");
    5. }
    6. }

    运行报错:

    错误:在类 java.lang.Integer 中找不到 main 方法,请将 main 方法定义为:

    public static void main(String[] args)

    否则 JavaFX 应用程序类必须扩展 javafx.application.Application
    ————————————————
    版权声明:本文为CSDN博主「干了这杯柠檬多」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_39404258/article/details/112065471