classLoader与读取配置文件

关于Thread.getContextClassLoader的使用场景问题

Thread context class loader存在的目的主要是为了解决parent delegation机制下无法干净的解决的问题。假如有下述委派链:
ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader

那么委派链左边的ClassLoader就可以很自然的使用右边的ClassLoader所加载的类。

但如果情况要反过来,是右边的ClassLoader所加载的代码需要反过来去找委派链靠左边的ClassLoader去加载东西怎么办呢?没辙,parent delegation是单向的,没办法反过来从右边找左边。

这种情况下就可以把某个位于委派链左边的ClassLoader设置为线程的context class loader,这样就给机会让代码不受parent delegation的委派方向的限制而加载到类了。

Thread.getContextClassLoader与Thread.getClassLoader()区别

在阅读spring boot启动时候的源码中,发现获取classLoader使用的是getContextClassLoader于是乎产生了疑问,这种获取ClassLoader的方式与我们最常见的通过Class.getClassLoader二者有什么区别?都是在什么场景下使用呢?

首先来看看getClassLoader()方法的注释:

Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.

主要的意思是getClassLoader()返回该类的类加载器,如果是bootstrap加载器加载的类返回Null,这种机制主要是为了安全考虑bootstrap是用来加载jvm核心库的加载器不允许暴露给用户操作,可能会引起类冲突的问题。

再来看一下getContextClassLoader()方法的说明:

Sets the context ClassLoader for this Thread. The context ClassLoader can be set when a thread is created, and allows the creator of the thread to provide the appropriate class loader, through getContextClassLoader, to code running in the thread when loading classes and resources.

这个方法返回叫线程上下文类加载器。这个加载器的类型指定的工作交给了线程创建者,创建者在创建线程之后用对应的setContextClassLoader()方法将适合的类加载器设置到线程中,那么线程中的代码就可以通过getContextClassLoader()获取到这个类加载器来加载类或者资源。如果不设置默认是系统类加载器就是 app ClassLoader()。

那么这个getContextLoader()方法在什么场景下使用呢? 因为很多框架为了做类的隔离会通过不同的ClassLoader来做类的隔离。假设框架提供了一个可扩展的服务接口这个接口的类加载器是app ClassLoader,但是这个接口是现实则交给使用者来扩展,那么实现类很有可能被自定义的ClassLoader加载导致appClassLoader无法找到实现类。如果在框架的层面希望能够获取到接口实现类就需要将实现类用getContextClassLoader获得的ClassLoader来加载。

总之,getContextClassLoader是原有代理类加载模式的一种补充。提供一种在子ClassLoader加载的类中获取父ClassLoader的实例来操作父加载器加载类的方法。

读取文件时FileInputStream和 "当前类.class.getClassLoader().getResourceAsStream(“filename”)"的区别

当读取的文件在硬盘中的某个文件夹中使用FileInputStream,若在编写程序时,比如自己的properties文件需要放到resource文件夹中,此时需要用到:当前类.class.getClassLoader().getResourceAsStream(“filename”)

properties文件中:# 为注释符号 所有语句前后,中间不可含有任何空格,不含有分号

在文件中避免使用中文:乱码


以下这些点没有验证:

props.load(new FileInputStream(“db.properties”)); 是读取当前目录的db.properties文件
getClass.getResourceAsStream(“db.properties”); 是读取当前类所在位置一起的db.properties文件
getClass.getResourceAsStream("/db.properties"); 是读取ClassPath的根的db.properties文件,注意ClassPath如果是多个路径或者jar文件的,只要在任意一个路径目录下或者jar文件里的根下都可以,如果存在于多个路径下的话,按照ClassPath中的先后顺序,使用先找到的,其余忽略.

System.out.println(System.getProperty("user.dir"));

输出目录是项目所在文件夹路径
如果打了jar包,输出时jar包所在文件夹路径。


个人备注:现在一般都有参数中心,像apollo等,一般会将这些配置参数,放在参数中心里面。所以,我个人在近几年的开发中,是没有遇到这种问题的。

彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理

设class包完整名为a.b.c
class.getResource 获取当前类加载器及其父加载器下a.b.c/下的资源
classloader.getResource 获取当前类加载器及其父加载器下所有jar包内资源(取第一个)

(而且会优先级为 父加载器——不一定本jar包(即执行代码的所在jar包)的子加载器所有jar包随机

classloader getresource jar包资源冲突情况,父亲为大,兄弟之间,谁先加载谁牛逼
【以下是未验证的】
案例设计图如下:
在这里插入图片描述

*注意,第二个结果“self”有失准确

特点:

1 father.conf这个文件3个jar包中都有,文本分别为father、self、brother,以此测试是否会从父加载器先加载资源

2 self.conf这个文件仅有S SB两个同属于一个类加载器jar包有,以此来测试究竟会加载哪个,是否有规律

3 brother.conf这个文件仅有SB有,但会在S的代码中去调用,以此来测试a jar包是否能调用到b jar包的资源,只要双方在同一个类加载器下

代码:

public class LcProperty {
 
    public static String read(String conf) throws IOException {
        System.out.println(LcProperty.class.getClassLoader().getResource(conf));
        InputStream inputStream = LcProperty.class.getClassLoader().getResourceAsStream(conf);
        byte [] bytes = new byte[20];
        inputStream.read(bytes);
        String out = new String(bytes);
        System.out.println(out);
        return out;
    }
}

这个类就是图中的S

String dirSub = "file:/Users/sunyuming/Documents/tool/jars//MySub-1.0.0-jar-with-dependencies.jar";
String dirSubBro = "file:/Users/sunyuming/Documents/tool/jars//MySubBrother-1.0.0-jar-with-dependencies.jar";
URL urlSub = new URL(dirSub);
URL urlSubBro = new URL(dirSubBro);
 
URLClassLoader cl1 = new URLClassLoader(new URL[]{urlSub, urlSubBro});
 
Class c1 = cl1.loadClass("lcproperty.LcProperty");
Method method1 = c1.getMethod("read", String.class);
method1.invoke(null, "father.conf");
method1.invoke(null, "self.conf");
method1.invoke(null, "brother.conf");
 
 
URLClassLoader cl2 = new URLClassLoader(new URL[]{urlSubBro, urlSub});【重要,放前面的先加载】
 
Class c2 = cl2.loadClass("lcproperty.LcProperty");
Method method2 = c2.getMethod("read", String.class);
method2.invoke(null, "father.conf");
method2.invoke(null, "self.conf");
method2.invoke(null, "brother.conf");
输出:

file:/Users/joyce/work/MyTest/MyMain/target/classes/father.conf
father
jar:file:/Users/sunyuming/Documents/tool/jars//MySub-1.0.0-jar-with-dependencies.jar!/self.conf
self
jar:file:/Users/sunyuming/Documents/tool/jars//MySubBrother-1.0.0-jar-with-dependencies.jar!/brother.conf
brother
file:/Users/joyce/work/MyTest/MyMain/target/classes/father.conf
father
jar:file:/Users/sunyuming/Documents/tool/jars//MySubBrother-1.0.0-jar-with-dependencies.jar!/self.conf
brother
jar:file:/Users/sunyuming/Documents/tool/jars//MySubBrother-1.0.0-jar-with-dependencies.jar!/brother.conf
brother

结论:

1 father.conf毫无疑问,每次都确定会先到父加载器寻找,从代码看也是如此

public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }
    if (url == null) {
        url = findResource(name);
    }
    return url;
}

2 self.conf 两次输出不一样,取决于类加载器先加载到哪个jar

3 brother.conf ,也毫无疑问的,SB的资源被从S的jar文件中的代码getResource到

参考文章:
1、https://www.cnblogs.com/549294286/p/5066869.html
2、https://blog.csdn.net/wp500/article/details/85762279
3、https://blog.csdn.net/weixin_42976232/article/details/90171281
4、https://blog.csdn.net/diquren/article/details/49000547
5、https://www.cnblogs.com/silyvin/p/10523050.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值