对于Java11不能转换为类java.net.URLClassLoader感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍java无法转换为内部表示,并为您提供关于ClassLoader.loa
对于Java 11 不能转换为类 java.net.URLClassLoader感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍java无法转换为内部表示,并为您提供关于ClassLoader.loadClass() throws java.lang.ClassNotFoundException、Java ClassLoader、Java classLoader spring LaunchedURLClassLoader、java ClassLoader 小解的有用信息。
本文目录一览:- Java 11 不能转换为类 java.net.URLClassLoader(java无法转换为内部表示)
- ClassLoader.loadClass() throws java.lang.ClassNotFoundException
- Java ClassLoader
- Java classLoader spring LaunchedURLClassLoader
- java ClassLoader 小解
Java 11 不能转换为类 java.net.URLClassLoader(java无法转换为内部表示)
如何解决Java 11 不能转换为类 java.net.URLClassLoader?
从 Java 8 迁移到 Java 11 时,出现以下错误:
Caused by: java.lang.classCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.urlclassloader (jdk.internal.loader.ClassLoaders$AppClassLoader and java.net.urlclassloader are in加载器''bootstrap''的模块java.base)
抛出错误的代码:
urlclassloader urlclassloader = (urlclassloader) Thread.currentThread().getContextClassLoader();
...
//urlclassloader urlclassloader = (urlclassloader) ClassLoader.getSystemClassLoader();
...
getmethod().invoke(urlclassloader,new Object[] { url });
谁能建议 Java 11 的兼容代码?
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)
ClassLoader.loadClass() throws java.lang.ClassNotFoundException
今天写一个 RMI 的应用,气死人了,弄半天,跑到 sun 官网去一看,告诉我是一个 BUG, 我流出了鼻血。
ToWork:
-Dsun.lang.ClassLoader.allowArraySyntax=true
点击打开链接
原文链接: http://blog.csdn.net/chengchanglun/article/details/7441034
Java ClassLoader
Java ClassLoader
Java ClassLoader 是 JVM 对类加载的基础,通过研究 ClassLoader,我们可以解惑 Tomcat 的多项目加载等机制。
什么是 ClassLoader
ClassLoader 是 JVM 加载 class 对象的一个类,通过这个类,可以把 class 载入到 JVM 虚拟机中,形成可执行代码。
默认 ClassLoader
一旦启动 JVM 虚拟机,就会默认加载几个 ClassLoader,作为基础加载类型。其基本关系为:
Bootstrap ClassLoader
该对象由 C/C++ 实现,用于加载 jre/lib 下的所有 JAR,特别是 rt.jar。 之后,会初始化一个 Launcher.java(sun.misc.Launcher)的对象,用于配置 ExtClassLoader 和 AppClassLoader。
ExtClassLoader
该对象主要是用于加载 /jre/lib/ext/*.jar 的数据
AppClassLoader
用于加载 系统环境变量中的 classpath 指向的 jar 以及 -cp | -jar 指定的 JAR 包
线程上下文 ClassLoader
在 Launcher 中,同时还会设置 AppClassLoader 作为线程上下文的 ClassLoader。
如果不使用 Thread#setContextClassLoader 修改对应线程的 ClassLoader,那么会继承父线程的 ClassLoader 对象
通过 线程上下文 ClassLoader,可以非常方便的加载 SPI 内容,比如说 JDBC。
双亲委派模型
Java 中 ClassLoader 的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:
-
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
-
当前 classLoader 的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到 bootstrp ClassLoader.
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
所以,默认的加载 class 优先级为:BootstrapClassLoader-> ExtClassLoader ->AppClassLoader 。
ClassLoader 空间命名机制
空间命名是指要确定某一个类,需要类的全限定名以及加载此类的 ClassLoader 来共同确定。公式为:
FULL_CLASS_IN_JVM_ID = CLASS_LOADER_ID + CLASS_NAME
也就是说即使两个类的全限定名是相同的,但是因为不同的 ClassLoader 加载了此类,那么在 JVM 中它是不同的类。
自定义 ClassLoader
我们可以通过编写 ClassLoader,加载一些特殊位置的代码,比如说从网络上获取。下面来介绍一下如何自定义 ClassLoader,查看 ClassLoader,可以发现几个重要的 API。
loadClass
这个方法,包含了读取缓存,双亲委托的功能,如果想打破双亲委托模型,则需要重写该方法。
触发 loadClass
的条件:
- 手动调用
loadClass
方法。 - 处理类 A 中的依赖类 B 的时候
for new B()
- JVM 查询加载类 A 的 ClassLoader 的 clz 缓存,判断 clz 缓存中是否存在类 B。
- clz 缓存中存在类 B,则直接返回类 B。
- clz 缓存中不存在类 B,则调用
loadClass
进行加载类 B。
注意:loadClass 会优先查询 clz 缓存。
findClass
这个方式,就是通过 className 获取具体 class 的方法了。可以看到 ClassLoader 中
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
就直接抛出异常,如果想自定义 ClassLoader,且不想打破双亲委托模型,那么只需要简单的重写该方法即可。
defineClass
通过这个方法,就可以把二进制文件转换为 JVM 内部的 Class 对象,且这个方法不能被重写,也应该是唯一的入口。
值得注意的是,即使你重写了 ClassLoader,如果加载 java. 的类,JVM 会拒绝你,因为只能通过 bootstrap 加载 java. 的 Class
常见的 ClassLoader API
// 通过当前类的类加载器加载(会执行初始化)
Class.forName("二进制名称");
Class.forName("二进制名称", true, this.getClass().getClassLoader());
// 通过当前类的类加载器加载(不会执行初始化)
Class.forName("二进制名称", false, this.getClass().getClassLoader());
this.getClass().loadClass("二进制名称");
// 通过系统类加载器加载(不会执行初始化)
ClassLoader.getSystemClassLoader().loadClass("二进制名称");
// 通过线程上下文类加载器加载(不会执行初始化)
Thread.currentThread().getContextClassLoader().loadClass("二进制名称");
new 关键字
为何会说道 new 关键字,因为 new 关键字,也是利用 ClassLoader 进行加载具体的类的!
new 使用的 ClassLoader
经过深入的研究发现,** 发现 new 的时候,采用的 ClassLoader 是关联到加载当前类的 ClassLoader。** 比如说:
public class Test{
public Test(){
print();
}
public void print(){
Runnable a = new Runnable(){
void run(){
System.out.println("Hello World");
}
}
}
}
现在我们通过自定义的 SelfClassLoader 加载 Test 类,并且进行 newInstance ()。那么,在上述代码中,new Runnable (){} 这个匿名类,也会通过自定义的 SelfClassLoader 加载
跨 ClassLoader 引用
我们定义了一个接口 (People),被 ClassLoader1 加载,然后我们定义了一个具体的实现类,被 ClassLoader2 加载。
那么,我们是否可以使用 CL1#People p = (CL1#People) CL2#Man 来进行强制转换呢?根据 ClassLoader 的双亲委派模式,其实在实例化 Man 的时候,因为 Man 的接口来自于 CL1#People,所以,这种转换是可以的,只要 ClassLoader2 的 parent 是 ClassLoader1
解决了这个问题,也就顺便的解决了 TOMCAT 接口都是由 COMMON ClassLoader 进行加载 Servlet API,而具体的 Servlet 实现由 WebClassLoader 进行加载,并且它们的转换是没有问题的。
AOP
Java
这里,再说一下 AOP 的知识,AOP 的一般实现有:
-
编译时 : 通过直接重写 class 类实现,AOP 功能,比较复杂
-
运行时 - 载入 Class 阶段:使用特殊的 ClassLoader,比较复杂
-
运行时 - 动态修改 Class : 读取某一个类的 byte [] 内容,然后使用 ClassLoader#defineClass 方法,申请一个类,然后使用它。
-
运行时 - JVM API:通过 Java 提供的 API,进行生成代理。
可以发现,前三种都是通过修改 class 的 byte 来达成 AOP 的目的。而最后一种,是通过 JVM 提供的 API 来达成 AOP 的。
C 语言
因为 C 语言的特性:基于偏移量寻址,而非符号,导致 C 语言基本上无法实现在运行时进行 AOP 操作,所以常见的方式:在编译源代码的时候,进行 AOP 处理。但是这种方式,还是有许多不足之处。
总结
通过了解 ClassLoader,可以学习到 JVM 的类加载机制,在具体的工作中,可以利用这些特性,比较方便的解决一些 ClassNotFoundException, CAST 异常等问题。
参考
深入探讨 Java 类加载器
Java 魔法堂:类加载器入了个门
Java Classloader 机制解析
Java classLoader spring LaunchedURLClassLoader

17 November 2018
Java classLoader
- 1 ClassLoder
- 1.1 ClassLoader 定义
- 1.2 ClassLoder 类
- 1.3 ClassLoader 继承关系与查找策略
- 1.4 classpath
- a. classpath 定义
- b. 怎样设置 classpath
- c. Class Path 和 Package Names
- d. 搜索路径与优先级
- 1.5 ClassLoder 并行加载
- 1.5 线程上下文类加载器
- 2 Spring 资源加载与 ClassLoader
- 2.1 Spring 对 resource 加载的封装
- 2.2 从 Spring 启动代码详解 ClassLoader
- a. SpringBoot 应用启动入口
- b. SpringBoot JarLauncher
- b. ClassPath 获取
- c. 创建 LaunchedURLClassLoader
- d. 进一步说 URLClassLoader 查找资源与类
- e. URLClassLoader findResource
- f. URLClassLoader findResources
- g. URLClassLoader 的 findClass
- h. ClassLoader 加载入口
- 3 总结
- 4 参考文档
当谈起 ClassLoader,首先被想起的是如题图中展示的 ClassLoader 复杂的继承关系及加载策略。其实除此之外,ClassLoader 还包含更丰富的细节。
1 ClassLoder
1.1 ClassLoader 定义
class loader 负责加载 class。将如 “java.lang.String” 的符合 Java 规范的 「Binary name」(https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#name)传给 ClassLoader 的实现类,实现类「定位」并「生成类信息」。这个加载过程中通常使用的加载策略是将传入的 「Binary name」转换为文件名(class file),然后在文件系统中读取这个 class file。
每个 Class 都持有加载它的 ClassLoader 的引用,这个也可从 Class 类的实现代码中发现。
1.2 ClassLoder 类
ClassLoader 具体实现可查看 java.lang
包下的 ClassLoader 类代码。
1.3 ClassLoader 继承关系与查找策略
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader ):也称为 AppClassLoader。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader () 来获取它。
文章首图展示了上面所述的继承关系。
任何需要定制 ClassLoader 加载行为的都可以实现 ClassLoader 这个类,改变默认的加载行为。ClassLoader 默认 加载 「classes」和 「resources」的策略是 「代理模式」,通常也称为双亲委派。ClassLoader 的代理加载策略可见下图。
下面用源码详细说明这种「代理加载策略」的详细实现。以 AppClassLaoder 为例,其实例可见 sun.misc.Launcher ,部分代码如下:
public Launcher() {
// 1. 实例 ExtClassLoader
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 实例 AppClassLoader,并通过参数 var1 设置 parent classLoader 为 ExtClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//...
}
AppClassLoader 并没有重写 Abstract 类 ClassLoader 的 getResource 方法,所以默认实现为如下,也就是先去 parent classLoader 也就是 ExtClassLoader 中加载,加载不到去 「virtual machine’s built-in class loader」也就是 「bootstrap class loader」中加载,最后才在自己的 classLoader 中加载。
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;
}
1.4 classpath
a. classpath 定义
classpath 是 java 运行时搜索 「class」和 「other resource(如 file/image…)」的「路径」。每一个 ClassLoader 都拥有自己的 classpath,作为资深查找资源的路径。
b. 怎样设置 classpath
如果不设置,classpath 默认值为当前目录(也就是「.」), 可以在执行 JDK Tool 时通过 「 -classpath 」或者 「-cp」选项指定,如
java -classpath classpath1:classpath2...
其中 「classpath1:classpath2…」 的值既可以是 .jar .zip 的归档文件,也可以是类似于 「/home/myDirectory」的 directory 路径。
c. Class Path 和 Package Names
Java 使用 package 的形式组织类,package 映射到文件系统中的 directory。但是和文件系统中的路径不同的是,package 是不可拆分的整体,举个例子。
在 utility.myapp
包下存在 Cool 类,位于文件系统的 C:\java\MyClasses\utility\myapp 下,如果希望运行 Cool,需要通过 java -classpath C:\java\MyClasses utility.myapp.Cool
方式。而通过 java -classpath C:\java\MyClasses\utility myapp.Cool
是无法加载到这个类的。
同样,如果 Cool 存在于 jar 或者 zip 包中,那么加载的方式就变为 java -classpath C:\java\MyClasses\myclasses.jar utility.myapp.Cool
。
如上策略的一个结果是,两个同样包路径下的同样的类,可以同时存在于两个不同的路径(jar 包)中。
d. 搜索路径与优先级
可以通过「;」分隔的形式指定多个路径,如 java -classpath C:\java\MyClasses;C:\java\OtherClasses ...
。其中路径声明的顺序决定了类与资源的查找顺序,按以上方式声明,那么 Java 会首先在 MyClasses 下查找类,如果没有找到需要加载的类,会继续在 OtherClasses 文件夹下查找。
1.5 ClassLoder 并行加载
默认 ClassLoader 这个 Abstract 类是支持并行加载的,但是其实现类,如果希望支持并行加载,需要在实例时通过 ClassLoader.registerAsParallelCapable
方法指定。如果自定义的 ClassLoader 的加载过程不不是像默认的代理模式这种具有严格层级的,需要将这个 ClassLoader 声明为并行加载的,避免在加载过程重出现死锁(loadClass 方法是持有锁的)。
1.5 线程上下文类加载器
引用一篇文章对上线文类加载器的解释。
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread 中的方法 getContextClassLoader () 和 setContextClassLoader (ClassLoader cl) 用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader (ClassLoader cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers 包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 类中的 newInstance () 方法用来生成一个新的 DocumentBuilderFactory 的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。
线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。
2 Spring 资源加载与 ClassLoader
2.1 Spring 对 resource 加载的封装
Spring 对 resource 加载做了封装,使用自身的 ResourceLoader 进行加载,ResourceLoader 根据资源不同形式将其封装为不同的 Resource 实现,但是最终的资源加载仍然使用底层的 ClassLoader。这部分的内容可见这篇文章。
2.2 从 Spring 启动代码详解 ClassLoader
a. SpringBoot 应用启动入口
当通过 java -jar 启动 jar 包时,启动入口为 MANIFEST.MF 描述文件中的 Main-Class 字段,如下为 SpringBoot FatJar 的描述文件。可见启动入口为 JarLauncher 中的 main 方法。
Manifest-Version: 1.0
Built-By: yanghaizhi
Start-Class: com.meituan.mdp.module.run.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.0.6.RELEASE
Created-By: Apache Maven 3.5.2
Build-Jdk: 1.8.0_60
Main-Class: org.springframework.boot.loader.JarLauncher
b. SpringBoot JarLauncher
JarLauncher 代码存在于 spring-boot 工程的 spring-boot-tools
下的 spring-boot-loader
中,其 main 方法如下
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
b. ClassPath 获取
上面 launch 方法内容如下,其中比较关键的代码为获取 classPath 的 getClassPathArchives 方法, 从以下代码中可以发现,符合两种条件的 路径 构成了 classpath:
- BOOT-INF/classes/ 路径
- BOOT-INF/lib/ 下的 每个 Jar
// 代码片段1
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
// 创建 classLoader,其中 getClassPathArchives() 为获取 classPath 的方法
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
// 代码片段2
/**
* 从以下代码中可以发现,符合两种条件的 路径 构成了 classpath
* 1. BOOT-INF/classes/ 路径
* 2. BOOT-INF/lib/ 下的 Jar
*
*/
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
return archives;
}
// 代码片段3
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
// 这个类实现了 iterator 接口,这里的 this 实际上是调用下方的 iterator 方法(代码片段4),也就是
// 遍历 jarFile(也就是 spring boot fat jar,也就是 spring boot plugin 插件 repacke生成的 jar包) 的 entries
// 注意!!!!!! jarFile 的 entries 是「有序的」,这个顺序是生成 Jar 的时候的写入 entry 的顺序,关于这里详细
// 描述见下方文字。
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
// 代码片段4
/**
* 这个类实现了 iterator 接口
*/
@Override
public Iterator<Entry> iterator() {
return new EntryIterator(this.jarFile.entries());
}
// 代码片段5
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
关于上面「代码片段 3」中所说的 jarFile 的 entries 是有序的,且顺序为 entry 写入顺序问题,可以通过 vi 这个 jar 包确认。SpringBoot 的测试类 JarLauncherTests
中,有生成 jarFile 的工具类,可以修改代码调整 entry 的写入顺序,下面是两种调整前后的对生成 jar 包 vi 的结果。
从 spring boot 生成的 FatJar 的 vi 结果看,其 Entry 顺序如下。
classpath 结论:
BOOT-INF/classes/ 路径 和 BOOT-INF/lib/ 下的 Jar 构成了 classpath,顺序为 classes 优先,lib(lib 下的每个 Jar 都是一个单独的 URL)更低。这个优先级在创建 classLoader 的时候会用到。见下方分析。
c. 创建 LaunchedURLClassLoader
我们关注 classLoader 创建的代码,如下。
/**
* 这个参数 List 有序,其顺序如上所述
*/
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
/**
* LaunchedURLClassLoader 使用有序的 URL 数组创建 classLoader
* LaunchedURLClassLoader 的父类为 URLClassLoader, 上面的 URL[] 直接传递给父类构造器
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
/**
* URLClassLoader 的构造器如下。使用 URLs 创建 URLClassLoader。在搜索 classes
* 和 resources 时候,URLClassLoader 使用的是 ClassLoader 的默认实现,也就是
* 逐层委托父 classLoader 查找后,查找不到后,开始查找自己。而查找自己的顺序为 URLs
* 这个数组的顺序,按照这个顺序不断向下查找。
*/
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
d. 进一步说 URLClassLoader 查找资源与类
URLClassLoader 类注释:
- This class loader is used to load classes and resources from a search
- path of URLs referring to both JAR files and directories. Any URL that
- ends with a ‘/’ is assumed to refer to a directory. Otherwise, the URL
- is assumed to refer to a JAR file which will be opened as needed.
像 URLClassLoader 类注释说的一样,URLClassLoader 是用来在指定的 URLs(可能是 Jar 或者 directory)搜索 class 和 resource 的工具。如果 URL 以 ‘/’ 结尾就默认为 directory,否则会被认为是一个 JAR 文件。进一步,以为 URLClassLoader 实际的查找是委托给 URLClassPath 的,如果是 directory,URLClassPath 在查找资源时会用 FileLoader 查找,如果是 Jar 文件,那么用 JarLoader 查找。
下面用 URLClassLoader 的代码来说明 resource 和 class 的加载过程。
e. URLClassLoader findResource
URLClassLoader 的所有查找操作其实都委托给 URLClassPath,URLClassPath 是在创建 URLClassLoader 时根据参数 URL [] 构建。
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
URLClassPath 的 findResource 方法
// 代码片段1
public URLClassPath(URL[] var1, URLStreamHandlerFactory var2) {
this.path = new ArrayList();
this.urls = new Stack();
// 实际的加载 class 或者 resource 文件的实现
// URL[] 中的每一个 URL 都对应实例一个 URLClassPath.Loader ,并且顺序和 参数 URL[] 顺序一致
// URLClassPath.Loader 使用 URL 作为查找的 basePath
this.loaders = new ArrayList();
this.lmap = new HashMap();
this.closed = false;
for(int var3 = 0; var3 < var1.length; ++var3) {
this.path.add(var1[var3]);
}
this.push(var1);
if (var2 != null) {
this.jarHandler = var2.createURLStreamHandler("jar");
}
}
// 代码片段2
public URL findResource(String var1, boolean var2) {
int[] var4 = this.getLookupCache(var1);
URLClassPath.Loader var3;
// 之前所述的 URLClassLoader 按照实例时传递的 URLs 的顺序搜索资源的功能由这里保证
// 由于 所有 URLClassPath.Loader 的顺序和 URL[] 一致,所以循环遍历查找时的循环顺序即是按照 URL[] 的顺序查找
// 当两个路径下存在相同的resource 时,会优先返回靠前 url 的资源
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
// 如果 URLClassPath.Loader 的实现为 JarLoader ,findResource 其实就是调用 jar.getJarEntry 方法查找 resource
URL var6 = var3.findResource(var1, var2);
if (var6 != null) {
return var6;
}
}
return null;
}
f. URLClassLoader findResources
通常 URLClassLoader 的 findResources 委托给 URLClassPath 的 findResources。
逻辑为在所有 URLs 列表中遍历查找包含 resource 的 url,并返回。
/**
* 其返回值为 Enumeration 的实例
* 遍历 index 拿到所有的 URLClassPath.Loader ,并不断遍历查找所有包含 resource 的 URL
*/
public Enumeration<URL> findResources(final String var1, final boolean var2) {
return new Enumeration<URL>() {
private int index = 0;
private int[] cache = URLClassPath.this.getLookupCache(var1);
private URL url = null;
private boolean next() {
if (this.url != null) {
return true;
} else {
// 不断遍历查找下一个包含属性的 URL
do {
URLClassPath.Loader var1x;
if ((var1x = URLClassPath.this.getNextLoader(this.cache, this.index++)) == null) {
return false;
}
// 这里调用的是 URLClassPath.Loader 的 findResource 方法
this.url = var1x.findResource(var1, var2);
} while(this.url == null);
return true;
}
}
public boolean hasMoreElements() {
return this.next();
}
public URL nextElement() {
if (!this.next()) {
throw new NoSuchElementException();
} else {
URL var1x = this.url;
this.url = null;
return var1x;
}
}
};
}
g. URLClassLoader 的 findClass
URLClassLoader 继承了 ClassLoader ,其未重写 LoadClass ,代表其默认使用 ClassLoader 的委派加载机制,也就是先向上委派,查找不到后使用 findClass 在自身 ClassLoader 中查找,以下是 findClass 逻辑。
和 URLClassLoader 的 findResource 方法不同点有三,也就是 class 查找和 resource 查找的不同点:
- 查找的 class 前会将 ‘.’ 替换为 ‘/’。如查找 ‘com.yhz.My’ ,那么实际委托给 URLClassPath 时是 ‘com/yhz/My.class’。因为 resource 本身路径使用 ‘/’ 区分(如 yhz/config.properties),所以并没有此步操作。
- findResource 委托给 URLClassPath 的 findResource 方法,findClass 委托给 URLClassPath 的 getResource 方法。前者返回的是一个 URL,后者返回的是一个 Resource (InputStream),也就是类字节码,字节码会传给 defineClass 用来解析为 Class。
- defineClass 方法会根据查找到的字节码解析为实际的 Class。
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 不同点1
String path = name.replace(''.'', ''/'').concat(".class");
// 不同点2
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 不同点3
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
针对上面所说的第二点不同,由于 URLClassPath 最终交给 URLClassPath.Loader 加载,而 URLClassPath.Loader 的 findResource 和 getResource 本质上并无不同,只不过是前者直接返回拼接的 URL,后者返回这个 URL 的流 InputStream。
所以,在 class 和 resource 的资源查找层面,二者并无不同。
h. ClassLoader 加载入口
SpringBoot 创建完自身使用的 LaunchedURLClassLoader 后,通过查找 MANIFEST 中的 Start-Class(也就是存在 @SpringBootApplication 注解的启动类) ,并执行 Start-Class 中的 main 方法。
// 其中 classLoader 参数为创建的 LaunchedURLClassLoader
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
// 设置当前线程的类加载器为创建的 LaunchedURLClassLoader
Thread.currentThread().setContextClassLoader(classLoader);
// run 的逻辑见下方代码片段
createMainMethodRunner(mainClass, args, classLoader).run();
}
通过创建的 LaunchedURLClassLoader 加载 Start-Class,并执行 SpringBoot 应用启动入口 main 方法。在加载 Start-Class 后,一系列其它这个类引用的类的加载也随即触发,通过 LaunchedURLClassLoader 或者其父 ClassLoader 加载,从而所有触发所有的被引用到的类的加载。
public void run() throws Exception {
// 加载 Start-Class。此时会触发其它被引用到的类的加载。
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 执行 SpringBoot 的入口方法
mainMethod.invoke(null, new Object[] { this.args });
}
3 总结
4 参考文档
- Oracle 的 ClassLoader 文档
- Oracle 的 ClassPath 文档
- 深入探讨 Java 类加载器 (IBM)
- SpringBoot 可执行 Jar 官方文档
java ClassLoader 小解
1、JVM 的类加载器有三个 按顺序依次是:BoostrapClassLoader、ExtClassLoader、AppClassLoader
2、ExtClassLoader 和 AppClassLoader 在 sun.misc.Launcher 类中做了定义
public class Launcher
{
static class AppClassLoader extends URLClassLoader
{
code......
}
static class ExtClassLoader extends URLClassLoader
{
code......
}
//只给了获取BoostrapClassLoader类加载路径方法,并没有明确定义
public static synchronized URLClassPath getBootstrapClassPath()
{
code......
}
}
2、写一个测试类看看程序启动三个 ClassLoader 都加载了那些东西
System.out.println("BootstrapClassLoader 的加载路径: ");
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls)
System.out.println(url);
System.out.println("-------------------------------------------");
URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
System.out.println(extClassLoader);
System.out.println("ExtClassLoader 的加载路径: ");
urls = extClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);
System.out.println("-------------------------------------------");
URLClassLoader appClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
System.out.println(appClassLoader);
System.out.println("AppClassLoader 的加载路径: ");
urls = appClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);
System.out.println("-------------------------------------------");
获得结果如下
BootstrapClassLoader 的加载路径:
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/resources.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/rt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/jsse.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/jce.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/charsets.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/lib/dt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/lib/tools.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/plugin.jar
-------------------------------------------
sun.misc.Launcher$ExtClassLoader@3e25a5
扩展类加载器 的加载路径:
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/ext/localedata.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.6.0_20/jre/lib/ext/sunpkcs11.jar
-------------------------------------------
sun.misc.Launcher$AppClassLoader@1a46e30
应用(系统)类加载器 的加载路径:
file:/D:/work/workspace_pafa4/dbOperator/bin/
file:/D:/work/workspace_pafa4/dbOperator/src/lib/ojdbc14.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/postgresql-9.4-1201.jdbc4.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/commons-logging-1.0.4.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/log4j-1.2.15.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/poi-3.9.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/commons-beanutils.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/commons-collections-3.1.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/commons-lang-2.2.jar
file:/D:/work/workspace_pafa4/dbOperator/src/lib/barcode4j.jar
看结果应该很清楚吧。 BootstrapClassLoader --- 系统根加载器 ExtClassLoader --- 加载 JVM 扩展所需的类 AppClassLoader --- 加载应用(项目)的类和所需的类(在 Eclipse 上看到的 Referenced Libraries)
BootstrapClassLoader 加载了 rt.jar、dt.jar 和 tools.jar,做什么用的呢?简单介绍一下
dt.jar 和 tools.jar 位于:{Java_Home}/lib/ 下,而 rt.jar 位于:{Java_Home}/jre/lib/ 下,其中: rt.jar 是 JAVA 基础类库,也就是你在 java doc 里面看到的所有的类的 class 文件 dt.jar 是关于运行环境的类库 tools.jar 是工具类库,编译和运行需要的都是 toos.jar 里面的类分别是 sun.tools.java.; sun.tols.javac.; 在 Classpath 设置这几个变量,是为了方便在程序中 import;Web 系统都用到 tool.jar。 rt.jar rt.jar 默认就在 Root Classloader 的加载路径里面的,而在 Claspath 配置该变量是不需要的;同时 jre/lib 目录下的 其他 jar:jce.jar、jsse.jar、charsets.jar、resources.jar 都在 Root Classloader 中 tools.jar tools.jar 是系统用来编译一个类的时候用到的,即执行 javac 的时候用到 javac XXX.java 实际上就是运行 java -Calsspath=% JAVA_HOME%\lib\tools.jar xx.xxx.Main XXX.java javac 就是对上面命令的封装 所以 tools.jar 也不用加到 classpath 里面 dt.jar dt.jar 是关于运行环境的类库,主要是 swing 的包 在用到 swing 时最好加上
扩展说一下 JDK 里面的一些 jar 的作用吧。
C:\Program Files\Java\jdk1.7.0_21 -- JDK的根目录,包含一些软件版权,声明,和自述文件,同时包含归档了的Java平台源代码包src.zip
C:\Program Files\Java\jdk1.7.0_21\src.zip -- 归档的Java源代码
C:\Program Files\Java\jdk1.7.0_21\include -- C 语言头文件 支持 用Java本地接口和Java虚拟机接口 来本机代码编程
C:\Program Files\Java\jdk1.7.0_21\lib -- Java开发工具要用的一些库文件,有包含了支持JDK工具的非核心类库tool.jar,dt.jar 归档的 BeanInfo 文件,用于告诉IDE这样显示java组件怎样让开发者在自己的应用程序中用户化它们
================C:\Program Files\Java\jdk1.7.0_21\jre================
C:\Program Files\Java\jdk1.7.0_21\jre -- JDK使用的Java运行环境(JRE)的根目录,这个运行环境实现了Java平台
C:\Program Files\Java\jdk1.7.0_21\jre\bin -- Java平台所要用的工具和库的可执行文件这些可执行文件和 /jdk1.7.0_21/bin相同的。这个路径不需要设置 PATH 环境变量 //Java 启动器工具充当了应用程序启动器(覆盖了1.1版本的JDK推出的旧版本JRE工具)
C:\Program Files\Java\jdk1.7.0_21\jre\bin\client -- 包含Java Hotspot(Java性能引擎) Client Virtual Machine 客户虚拟机要用的DLL文件
C:\Program Files\Java\jdk1.7.0_21\jre\bin\server -- 包含Java Hotspot(Java性能引擎) Server Virtual Machine 服务器虚拟机要用的DLL文件 ----JDK 比 JRE C:\Program Files\Java\jre7\bin多一个server端的java虚拟机。即这个folder “Server” 不存在于JRE下。
C:\Program Files\Java\jdk1.7.0_21\jre\lib -- JRE要用的代码库,属性设置,资源文件。
C:\Program Files\Java\jdk1.7.0_21\jre\lib\rt.jar -- Java 引导类库(java 核心APIRunTime类)
C:\Program Files\Java\jdk1.7.0_21\jre\lib\charsets.jar -- 字符转换类库
C:\Program Files\Java\jdk1.7.0_21\jre\lib\ext -- 默认的Java平台扩展安装环境
C:\Program Files\Java\jdk1.7.0_21\jre\lib\ext\localedata.jar -- ava.text 和 java.util包要用到的地区数据
C:\Program Files\Java\jdk1.7.0_21\jre\lib\security -- 包含安全管理文件,有安全规则(java.policy)和安全属性文件(java.security)
C:\Program Files\Java\jdk1.7.0_21\jre\lib\applet -- Java applets 要的Jar包,可以放到lib/applet/目录,可以节省 applet 类装载器从本地文件系统装载 大的applets 所需的applet类时间,减少从网上下载具有相同的保护的时间。
C:\Program Files\Java\jdk1.7.0_21\jre\lib\fonts -- 包含平台所需的TrueType字体文件
================C:\Program Files\Java\jdk1.7.0_21\db================
C:\Program Files\Java\jdk1.7.0_21\db -- db目录,纯Java开发的数据库 Apache Derby,是一个开源的100%Java开发的关系数据库
[有关 Java DB 的信息](http://developers.sun.com/prodtech/javadb/)
[有关 Derby 的文档](http://db.apache.org/derby/manuals/index.html)
================C:\Program Files\Java\jdk1.7.0_21\bin================
C:\Program Files\Java\jdk1.7.0_21\bin -- JDK包含的一些开发工具执行文件
C:\Program Files\Java\jdk1.7.0_21\bin\javac.exe -- 基本工具 - Java语言编译器, 将Java源代码转换成字节码
C:\Program Files\Java\jdk1.7.0_21\bin\java.exe -- 基本工具 - Java应用程序启动器,直接从类文件执行Java应用程序字节代码
C:\Program Files\Java\jdk1.7.0_21\bin\javadoc.exe -- 基本工具 - Java API 文档生成器,从源码注释中提取文档
C:\Program Files\Java\jdk1.7.0_21\bin\apt.exe -- 基本工具 - java 注释处理器
C:\Program Files\Java\jdk1.7.0_21\bin\appletviewer.exe -- 基本工具 - java applet 小程序浏览器,一种执行HTML文件上的Java小程序的Java浏览器。
C:\Program Files\Java\jdk1.7.0_21\bin\jar.exe -- 基本工具 - java文件压缩打包工具
C:\Program Files\Java\jdk1.7.0_21\bin\jdb.exe -- 基本工具 - Java 调试器,debugger,查错工具
C:\Program Files\Java\jdk1.7.0_21\bin\javah.exe -- 基本工具 - C 头文件和stub生成器,用于写本地化方法,例如生产JNI样式的头文件。产生可以调用Java过程的C过程,或建立能被Java程序调用的C过程的头文件
C:\Program Files\Java\jdk1.7.0_21\bin\javap.exe -- 基本工具 - class文件反编译工具,显示编译类文件中的可访问功能和数据,同时显示字节代码含义。
C:\Program Files\Java\jdk1.7.0_21\bin\extcheck.exe -- 基本工具 - 用于检测jar包中的问题
C:\Program Files\Java\jdk1.7.0_21\bin\keytool.exe -- 安全工具 - 管理密钥库和证书.
C:\Program Files\Java\jdk1.7.0_21\bin\jarsigner.exe -- 安全工具 - 生产和校验JAR签名
C:\Program Files\Java\jdk1.7.0_21\bin\policytool.exe -- 安全工具 - 有用户界面的规则管理工具
C:\Program Files\Java\jdk1.7.0_21\bin\kinit.exe.exe -- 安全工具 - 用于获得和缓存网络认证协议Kerberos 票证的授予票证
C:\Program Files\Java\jdk1.7.0_21\bin\klist.exe.exe -- 安全工具 - 凭据高速缓存和密钥表中的 Kerberos 显示条目
C:\Program Files\Java\jdk1.7.0_21\bin\ktab.exe.exe -- 安全工具 - 密钥和证书管理工具
C:\Program Files\Java\jdk1.7.0_21\bin\native2ascii.exe -- Java国际化工具 - 将文本转化为 Unicode Latin-1。[详情参考](http://java.sun.com/javase/6/docs/technotes/tools/windows/native2ascii.html)
C:\Program Files\Java\jdk1.7.0_21\bin\rmic.exe -- 远程方法调用工具 - 生成远程对象的stubs and skeletons(存根和框架)
C:\Program Files\Java\jdk1.7.0_21\bin\rmid.exe -- 远程方法调用工具 - Java 远程方法调用(RMI:Remote Method Invocation) 活化系统守护进程
C:\Program Files\Java\jdk1.7.0_21\bin\rmiregistry.exe -- 远程方法调用工具 - Java 远程对象注册表
C:\Program Files\Java\jdk1.7.0_21\bin\serialver.exe -- 远程方法调用工具 - 返回类的 serialVersionUID.
C:\Program Files\Java\jdk1.7.0_21\bin\tnameserv.exe -- Java IDL and RMI-IIOP 工具 - Provides access to the naming service.
C:\Program Files\Java\jdk1.7.0_21\bin\idlj.exe -- Java IDL and RMI-IIOP 工具 - 生产映射到OMG IDL接口可以使Java应用程序使用CORBA的.java文件
C:\Program Files\Java\jdk1.7.0_21\bin\orbd.exe -- Java IDL and RMI-IIOP 工具 - 为客户可以在CORBA环境下透明的定位和调用服务器的稳定的对象提供支持
C:\Program Files\Java\jdk1.7.0_21\bin\servertool.exe -- Java IDL and RMI-IIOP 工具 - 为应用程序提供易于使用的接口用于注册,注销,启动,关闭服务器
C:\Program Files\Java\jdk1.7.0_21\bin\pack200.exe -- Java 部署工具 - 使用java gzip压缩工具将JAR文件转换为压缩的pack200文件,生产打包文件是高度压缩的JAR包,可以直接部署,减少下载时间
C:\Program Files\Java\jdk1.7.0_21\bin\unpack200.exe -- Java 部署工具 - 解包pack200文件为JARs
C:\Program Files\Java\jdk1.7.0_21\bin\htmlconverter.exe -- Java 插件工具 - Java Plug-in HTML转换器 htmlconverter -gui 可以启动图形界面
C:\Program Files\Java\jdk1.7.0_21\bin\javaws.exe -- Java web 启动工具 - Java web 启动命令行工具
C:\Program Files\Java\jdk1.7.0_21\bin\jvisualvm.exe -- Java 故障检修,程序概要分析,监视和管理工具 - 一个图形化的Java虚拟机,不说了 大家研究一下就发现太酷了.这是想了解JVM的人的神器
[参考](http://java.sun.com/javase/6/docs/technotes/guides/visualvm/index.html)
C:\Program Files\Java\jdk1.7.0_21\bin\jconsole.exe -- Java 故障检修,程序概要分析,监视和管理工具 -java监视台和管理控制台,图形界面的功能太强大了,运行一下就知道 ,不想多说,看了就知道
C:\Program Files\Java\jdk1.7.0_21\bin\schemagen.exe -- Java web 服务工具 - Java构架的XML Schema生成器
C:\Program Files\Java\jdk1.7.0_21\bin\wsgen.exe -- Java web 服务工具 - 生成 JAX-WS
C:\Program Files\Java\jdk1.7.0_21\bin\wsimport.exe -- Java web 服务工具 - 生成 JAX-WS
C:\Program Files\Java\jdk1.7.0_21\bin\xjc.exe -- Java web 服务工具 - 绑定编译器
C:\Program Files\Java\jdk1.7.0_21\bin\jps.exe -- 监视工具 - JVM Process Status 进程状态工具。列出目标系统的HotSpot JJVM , 监视Java虚拟机的性能,不支持Windows 98 和Windows ME 平台
C:\Program Files\Java\jdk1.7.0_21\bin\jstat.exe -- 监视工具 - 按照命令行的具体要求记录和收集一个JVM的性能数据
C:\Program Files\Java\jdk1.7.0_21\bin\jstatd.exe -- 监视工具 - JVM jstat 的守护进程
C:\Program Files\Java\jdk1.7.0_21\bin\jinfo.exe -- 故障检测和修理工具 - 配置或打印某个Java进程VM flag
C:\Program Files\Java\jdk1.7.0_21\bin\jhat.exe -- 故障检测和修理工具 - 堆储存查看器
C:\Program Files\Java\jdk1.7.0_21\bin\jmap.exe -- 故障检测和修理工具 - Java内存图
C:\Program Files\Java\jdk1.7.0_21\bin\jsadebugd.exe -- 故障检测和修理工具 - Java 的 Serviceability Agent Debug的守护进程
C:\Program Files\Java\jdk1.7.0_21\bin\jstack.exe -- 故障检测和修理工具 - Java堆栈跟踪
C:\Program Files\Java\jdk1.7.0_21\bin\jrunscript.exe -- Java脚本工具 - 运行脚本
我们今天的关于Java 11 不能转换为类 java.net.URLClassLoader和java无法转换为内部表示的分享就到这里,谢谢您的阅读,如果想了解更多关于ClassLoader.loadClass() throws java.lang.ClassNotFoundException、Java ClassLoader、Java classLoader spring LaunchedURLClassLoader、java ClassLoader 小解的相关信息,可以在本站进行搜索。
本文标签: