下面的代码是否破坏Java中类加载器的可见性原则?

作者站长头像
站长
· 阅读数 5

tomcat加载spring-web模块的时候,需要发现spring-web的提供的实现类,就用到了SPI机制,会使用ServiceLoader#load方法拿到所有实现ServletContainerInitializer接口的类.

我看到一个文章说,这会破坏Java的类加载过程的可见性原则.

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        
        // java.util.ServiceLoader.LazyIterator#nextService
        // c = Class.forName(cn, false, loader);        
        return ServiceLoader.load(service, cl);
}

上面的代码我看实际上使用了加载器sun.misc.Launcher.AppClassLoader来加载实现了load方法拿到所有实现ServletContainerInitializer接口的类.

sun.misc.Launcher#Launcher

public Launcher() {
        ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

和下面的代码效果是一样的吧?

return ServiceLoader.load(service, null);
  1. 为什么违背了可见性原则?
回复
1个回答
avatar
test
2024-06-29

1、首先要理解为什么会说SPI破坏了双亲委派?其实就是诸如rt.jar里定义的接口是由BootstrapClassLoader类加载器完成加载的,但是这些接口的实现类BootstrapClassLoader类加载器找不到,因为它们是由第三方的jar包实现的,是在classPath下的,应该是由AppClassLoader类加载器来加载的,对于BootstrapClassLoader来说它还能委派给哪个父加载器呢?可是又不能向下委派,所以就只能指定一个线程上下文类加载器 (Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(AppClassLoader),所以最后SPI可以正常运行,但是我们说它打破了双亲委派机制2、sun.misc.Launcher.AppClassLoader的顶级抽象类就是java.lang.ClassLoader,所以你说“这里没有执行java.lang.ClassLoader#loadClass(java.lang.String, boolean)双亲委派代码”其实最后还是执行的了,是SPI交由AppClassLoader已经找到了第三方的实现类把它加载进JVM了,所以不需要继续向上委托父加载器了3、SPI就是违反双亲委派比较经典的例子呀,具体一点说——常常会被拿来作SPI的例子的JDBC的例子,JAVA只给了操纵数据库的接口,具体要怎么实现是各家厂商自己的事;事实上你也可以自己写一个类加载器继承java.lang.ClassLoader,重写它的loadClass方法,就算是打破双亲委派机制了

回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容