类加载器的根本作用,即从包含字节代码的字节流中定义出虚拟机中的Class类对象。
一个java类被加载之后,可以通过Class对象的getClassLoader方法获取加载它的类加载器对象。也可以通过继承ClassLoader类实现自己的类加载器。ClassLoader除了可以加载java类之外,还可以加载相关的文件资源
- loadClass方法,参数为java类名称,返回值为Class对象
- defineClass方法用于,从字节代码中定义出Java类Class对象,当使用类加载器对象的loadClass方法加载一个java类时,则称这个类加载器对象为该Java类的初始类加载器,当成功得到一个Class对象,那么虚拟机就会记录类加载器对象和他加载的Class对象之间的关联记录下来,在加载其他java类时,会首先查询这个缓存关系
自定义类加载器
创建自定义类加载器,只需要继承ClassLoader即可,覆写其中某些方法。
- defineClass 为 final ,不允许覆写
- protected Class<?> loadClass(String name, boolean resolve):该方法包含默认的双亲类加载器优先策略,当不需要双亲优先策略可以覆写该类
- findLoadedClass:类加载器过程中,会记录已经加载的java类的类加载器,用于缓存
- findClass: 当通过代理模式无法使用双亲类加载器成功加载java类时,findClass方法会调用,比如定义从远程url中加载class文件
- resolveClass: 链接一个定义好的Class类的对象
自定义累加器主要用途
- 对java类的字节代码进行特殊的查找和处理,如java字节码存在远程服务器,或字节码经过加密处理
- 利用类加载器产生的隔离特性满足特殊需求
线程上下文类加载器
Thread类有两个方法用来获取和设置当前线程的上下文类加载器。如果一个线程没有显示设置其上下文类加载器,则使用其父线程的上下文类加载器。程序启动的第一个线程的上下文类加载器 默认是 系统类加载器
上下文类加载器,主要是方便共享classLoader,一个使用场景是SPI?
关于SPI 与 线程上下文加载器 ,推荐这篇文章 Java线程上下文加载器与SPI
看完文章,给我的理解就是,由于SPI 的接口一般都是由启动类加载器加载,而SPI实现类都是由 系统类加载器加载,如果还是默认双亲模式,那么接口无法加载这些实现类,而服务发现机制,让接口可以发现实现类 并加载,即扩大了class加载的范围?
Class.forName
Class.forName方法与 ClassLoader类区别在于Class.forName方法可以初始化java类。意味着Java类中的静态变量会初始化,静态代码块也会被执行!
Class.for("xx.yy.zz");//会初始化java类,静态代码块也会执行
classLoader.loadClass("xx.yy.zz");//不会执行静态代码块
加载资源
类加载器 除了可以加载Java类以外,还可以加载和Java类相关的资源文件。使用类加载器来加载资源的好处是可以解决资源文件的存放路径的不固定性。不需要像Java 文件API那样需要绝对路径。
- getResouce:类似于loadClass方法,也会有类似双亲委托的机制。如果通过委托找不到资源,就调用findResource方法来查找
public URL getResource(String var1) {
URL var2;
if (this.parent != null) {
var2 = this.parent.getResource(var1);
} else {
var2 = getBootstrapResource(var1);
}
if (var2 == null) {
var2 = this.findResource(var1);
}
return var2;
}
- findResouce方法类似于findClass方法。如果类加载器有自定义的资源加载策略,需要覆写该方法
- getResources()方法,这个方法会返回所有具有该名称的资源文件
使用classLoader 查找资源需要书写资源所在的包名
Class类也有相关获取资源的方法,方法名也为getResource 和 getResourceAsStream.但会使用资源名称转化。如果资源名称以‘/’开头,则会去掉 ‘/’;
否则自动在资源名称前加上class类对象所在包的名称
类似
ReadFile file = new ReadFile();
ClassLoader classLoader = file.getClass().getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("META-INF/spring.factories");
Properties p1 = new Properties();
p1.load(resourceAsStream);
System.out.println(p1.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration"));
InputStream resource = file.getClass().getResourceAsStream("/META-INF/spring.factories");
Properties p2 = new Properties();
p2.load(resource);
System.out.println(p2.getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration"));