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

2023-08-01 0 1,982

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、首先要理解为什么会说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;
    }
}
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

1. JK下载官网所有资源来源于开发团队,加入会员即可下载使用!如有问题请联系右下角在线客服!
2. JK下载官方保障所有软件都通过人工亲测,为每位会员用户提供安全可靠的应用软件、游戏资源下载及程序开发服务。
3. JK开发团队针对会员诉求,历经多年拥有现今开发成果, 每款应用程序上线前都经过人工测试无误后提供安装使用,只为会员提供安全原创的应用。
4. PC/移动端应用下载后如遇安装使用问题请联系右下角在线客服或提交工单,一对一指导解决疑难。

JK软件下载官网 技术分享 下面的代码是否破坏Java中类加载器的可见性原则? https://www.jkxiazai.com/2309.html

JK软件应用商店是经过官方安全认证,保障正版软件平台

相关资源

官方客服团队

为您解决烦忧 - 24小时在线 专业服务