这个问题经常出现在编写框架代码 , 需要动态加载很多类和资源的时候 . 通常当你需要动态加载资源的时候 , 你至少有三个 ClassLoader 可以选择 :
² 系统类加载器或叫作应用类加载器 (system classloader or application classloader)
² 当前类加载器
² 当前线程类加载器
上面的问题指的是最后一种类加载器 . 哪种类加载器是正确的选择呢 ?
第一种选择可以很容易地排除 : 系统类加载器 (system classloader). 这个类加载器处理 -classpath 下的类加载工作 , 可以通过 ClassLoader.getSystemClassLoader() 方法调用 . ClassLoader 下所有的 getSystemXXX() 的静态方法都是通过这个方法定义的 . 在你的代码中 ,你应该尽量少地调用这个方法 , 以其它的类加载器作为代理 . 否则你的代码将只能工作在简单的命令行应用中 , 这个时候系统类加载器 (system classloader) 是 JVM 最后创建的类加载器 . 一但你把代码移到 EJB, Web 应用或 Java Web Start 应用中 , 一定会出问题 .
所以我们来看第二种选择 : 当前上下文环境下的类加载器 . 根据定义 , 当前类加载器就是你当前方法所属的类的加载器 . 在运行时类之间动态联编 , 及调用 Class.forName,() Class.getResource() 等类似方法时 , 这个类加载器会被隐含地使用 . It is also used by syntactic constructs like X.class class literals.
线程上下文类型加载器是在Java 2平台上被引入的. 每一个线程都有一个类加载器与之对应(除非这个线程是被本地代码创建的). 这个类加载器是通过Thread.setContextClassLoaser()方法设置的. 如果你不在线程构造后调用这个方法, 这个线程将从它的父线程中继承相应的上下文类加载器. 如果在整个应用中你不做任何特殊设置,所有的线程将都以系统类加载器(system classloader)作为自己的线程上下文类加载器.自从Web和J2EE应用服务器使用成熟的类加载器机制来实现诸如JNDI, 线程池, 组件热部署等功能以来, 这种在整个应用中不做任何线程类加载器设置的情况就很少了.
为什么线程上下文类加载器存在于如此重要的位置呢? 这个概念在J2SE中的引入并不引人注目. 很多开发人员对这一概念迷惑的原因是Sun公司在这方面缺乏适当的指引和文档.
事实上, 上下文类加载器提供了类加载机制的后门, 这一点也在J2SE中被引入了. 通常,在JVM中的所有类加载器被组织成了有继承层次的结构, 每一个类加载器(除了引导JVM的原始类加载器)都有一个父加载器. 每当被请示加载类时, 类加载器都会首先请求其父类加载器, 只有当父类加载器不能加载时, 才会自己进行类加载.
有时候这种类加载的顺序安排不能正常工作, (此处的意思是:正常情况下都是从子类加载器到根类加载器请求,万一有根类里需要加载子类时,这种顺序就不能满足要求,就要有一条反向的通道,即得到子类加载器,这样就用到了thread context classloader,因为通过thread.getcontextclassloader()可以得到子类加载器).通常当必须动态加载应用程序开发人员提供的资源的时候. 以JNDI为例: 它的内容(从J2SE1.3开始)就在rt.jar中的引导类中实现了, 但是这些JNDI核心类需要动态加载由独立厂商实现并部署在应用程序的classpath下的JNDI提供者. 这种情况就要求一个父classloader(本例, 就是引导类加载器)去加载对于它其中一个子classloader(本例, 系统类加载器)可见的类. 这时通常的类加载代理机制不能实现这个要求. 解决的办法(workaround)就是, 让JNDI核心类使用当前线程上下文的类加载器,这样, 就基本的类加载代理机制的相反方向建立了一条有效的途径.
另外, 上面一段可能让你想起一些其它的事情: XML解析Java API(JAXP). 是的, 当JAXP只是J2SE的扩展进, 它很自然地用当前类加载器来引导解析器的实现. 而当JAXP被加入到J2SE1.4的核心类库中时, 它的类加载也就改成了用当前线程类加载器, 与JNDI的情况完全类似(也使很多程序员很迷惑). 明白为什么我说来自Sun的指导很缺乏了吧?
在以上的介绍之后, 我们来看关键问题: 这两种选择(当前类加载器和当前线程类加载器)都不是在所有环境下都适用. 有些人认为当前线程类加载器应该成为新的标准策略. 但是, 如果这样, 当多个线程通过共享数据进行交互的时, 将会呈现出一幅极其复杂的类加载的画面, 除非它们全部使用了同一个上下文的类加载器. 进一步说, 在某些遗留下来的解决方案中, 委派到当前类加载器的方法已经是标准. 比如对Class.forName(String)的直接调用(这也是我为什么推荐尽量避免对这个方法进行调用的原因). 即使你努力去只调用上下文相关的类加载器, 仍然会有一些代码会不由你控制. 这种不受控制的类加载委派机制是混入是很危险的.
更严重的问题, 某些应用服务器把环境上下文及当前类加载器设置到不同的类加载器实例上, 而这些类加载器有相同的类路径但却没有委派机制中的父子关系. 想想这为什么十分可怕. 要知道类加载器定义并加载的类实例会带有一个JVM内部的ID号. 如果当前类加载器加载一个类X的实例, 这个实例调用JNDI查找类Y的实例, 些时的上下文的类加载器也可以定义了加载类Y实例. 这个类Y的定义就与当前类加载器看到的类Y的定义不同. 如果进行强制类型转换,则产生异常.
这种混乱的情况还将在Java中存在一段时间. 对于那些需要动态加载资源的J2SE的API, 我们来猜想它们的类加策略. 例如:
Ø JNDI 使用线程上下文类加载器
Ø Class.getResource() 和Class.forName()使用当前类加载器
Ø JAXP(J2SE 1.4 及之后)使用线程上下文类加载器
Ø java.util.ResourceBundle 使用调用者的当前类加载器
Ø URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
Ø Java 序列化API默认使用调用者当前的类加载器
这些类及资源的加载策略问题, 肯定是J2SE领域中文档最及说明最缺乏的部分了.
下面是一个动态加载jar的方法:
public class WebModelUtil { private static Class<URLClassLoader> clazz; private static final Logger log = Logger.getLogger(DtiWebModelUtil.class); /** * 动态加载jar文件。 * @param jarPath jar文件的路径。 * 如:/app/web/jar/XXXXXXX.jar */ public static void loadJar(String jarPath) { URLClassLoader urlLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); clazz = URLClassLoader.class; try { // 调用URLClassLoader的私有方法:addURL,来动态加载jar文件。 Method method = clazz.getDeclaredMethod("addURL", new Class[] { URL.class }); method.setAccessible(true); jarPath = "file:///"+jarPath; System.out.println(jarPath + " == [jarPath]"); method.invoke(urlLoader, new URL(jarPath)); log.debug("系统成功动态加载jar类库:"+jarPath); } catch (Exception e) { log.error("加载DTI构件化jar包失败!", e); } } }
相关推荐
需要注意的是,`URLClassLoader`默认只加载指定的URLs,如果需要加载父类加载器的类,可以使用`ClassLoader.getSystemClassLoader()`或者`Thread.currentThread().getContextClassLoader()`。此外,为了安全考虑,...
### Spring Boot 动态加载 Jar 包到容器中并映射为接口的技术解析 #### 一、背景介绍 在实际的开发过程中,我们经常会遇到这样的需求:系统需要根据不同的业务场景来动态地加载不同的功能模块或者组件。这些功能...
Java类加载器是Java虚拟机(JVM)的重要组成部分,它们负责将Java源代码编译后的字节码(.class文件)转化为可执行的内存表示,即`java.lang.Class`对象,进而创建类实例。Java类加载器按照特定的规则进行工作,包括...
Java 动态添加外部jar包到classpath是指在Java应用程序中动态地加载外部jar包到classpath中,以便在不重新启动服务器的情况下使用最新的代码。这项技术在项目开发过程中非常有用,因为它可以让开发者在不影响应用...
在Java编程中,读取jar包内的文件是一个常见的需求,特别是在运行时动态加载资源或类文件时。Java提供了一种内置的方式,通过`java.util.jar`包中的`JarFile`和`JarInputStream`类来实现这个功能。下面将详细介绍...
总的来说,正确加载JAR包中的资源文件需要理解Java类路径和资源加载机制。根据具体需求,可以选择合适的API和方法。提供的源码测试项目可能包含了上述方法的实现,通过分析和学习这些代码,可以加深对这一主题的理解...
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("path/to/compiled/classes").toURI().toURL()}, Thread.currentThread().getContextClassLoader()); Class<?> clazz = Class.forName(...
总结来说,Java的动态编译涉及使用`JavaCompiler`接口和`StandardJavaFileManager`来调用Javac,以及在运行时加载和实例化新生成的类。这个功能为开发者提供了更大的灵活性,可以在运行时根据需求编译和执行代码。在...
每个线程都有一个与之关联的类加载器,可以通过Thread.currentThread().getContextClassLoader()获取。这在某些情况下非常有用,例如在Web应用服务器中加载特定Web应用的类。 在Web容器(如Tomcat)中,每个Web应用...
4. 实例化`URLClassLoader`,它是一个自定义的类加载器,这里通过`URLClassLoader`的构造函数将jar文件的URL加入,并用`Thread.currentThread().getContextClassLoader()`获取当前线程的类加载器作为父类加载器,以...
例如,在JDBC编程中,我们需要加载JDBC驱动程序时,可能需要使用Thread.currentThread().getContextClassLoader()获取的类加载器来加载驱动程序的类。这样可以确保正确地加载驱动程序的类。 Java中的类加载器具有...
这个方法使用了 JarFile 和 JarEntry 来获取 jar 文件中的所有类,然后使用 URLClassLoader.loadClass 方法加载类,并使用 getMethods 方法获取类中的方法。 反射的应用 反射机制有很多应用,例如: * 动态代理:...
- 在运行Java程序时,JVM会查找类路径(Classpath)中定义的路径来加载类和其他资源。`src`目录下的文件在构建后会被包含到Classpath中。 3. **使用Java I/O流读取文件**: - Java提供了一系列的I/O流类来处理...
XStream也支持将XML数据恢复为Java对象,这对于从XML文件或网络响应中加载数据非常有用。 1. **设置别名**:与序列化相同,你需要设置XML标签的别名,以便XStream知道如何映射回正确的Java类型。 2. **从XML恢复对象...
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 或者 ClassLoader classLoader = MyClass.class.getClassLoader(); ``` 这两种方式都可以获取到当前线程或特定类的`...
这通常涉及到`ClassLoader`的使用,例如`Thread.currentThread().getContextClassLoader().getResource()`可以返回指定资源的URL。 `UrlUtil`类的使用场景可能包括但不限于构建HTTP请求的URL、解析URL获取主机名、...
5. `Thread.currentThread().getContextClassLoader().getResource("")`:获取当前线程上下文类加载器的根路径,如`file:/D/workspace/jbpmtest3/bin/`。 6. `ServletActionContext.getServletContext().getRealPath...
在Java中,虽然我们可以使用相对路径,但最终Java内部会将其转换为绝对路径来定位资源。API提供的便利方法,如`File`类的各种构造函数,实际上都是在背后帮我们构建绝对路径。 在Web应用开发中,特别是Servlet、...
`Thread.currentThread().getContextClassLoader()`返回当前线程的上下文类加载器(通常是AppClassLoader),`System.class.getClassLoader()`返回加载`System`类的类加载器(BootstrapClassLoader),而`...