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

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. 为什么违背了可见性原则?
阅读 2.3k
2 个回答

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方法,就算是打破双亲委派机制了

public class WebappClassLoader extends ClassLoader {

    public WebappClassLoader(ClassLoader parent) {
        super(parent);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> result = findLoadedClass(name);

        if (result == null) {
            try {
                // 先尝试自己加载
                result = findClass(name);
            } catch (ClassNotFoundException e) {
                // 如果自己加载失败,再委派给父类加载器
                result = super.loadClass(name);
            }
        }

        return result;
    }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题