这是一个很常见的问题,但答案却很难回答。这个问题通常在需要动态加载类和资源的系统编程时会遇到。总的说来动态加载资源时,往往需要从三种类加载器里选择:系统或说程序的类加载器、当前类加载器、以及当前线程的上下文类加载器。在程序中应该使用何种类加载器呢?
系统类加载器通常不会使用。此类加载器处理启动应用程序时classpath指定的类,可以通过
ClassLoader.getSystemClassLoader()来获得。所有的ClassLoader.getSystemXXX()接口也是通
过这个类加载器加载的。一般不要显式调用这些方法,应该让其他类加载器代理到系统类加载器上。由于系统类加载器是JVM最后创建的类加载器,这样代码只会
适应于简单命令行启动的程序。一旦代码移植到EJB、Web应用或者Java Web Start应用程序中,程序肯定不能正确执行。
因此一般只有两种选择,当前类加载器和线程上下文类加载器。当前类加载器是指当前方法所在类的加载器。这个类加载器是运行时类解析使用的加载
器,Class.forName(String)和Class.getResource(String)也使用该类加载器。代码中X.class的写法使
用的类加载器也是这个类加载器。
线程上下文类加载器在Java
2(J2SE)时引入。每个线程都有一个关联的上下文类加载器。如果你使用new
Thread()方式生成新的线程,新线程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系
统类加载器作为上下文类加载器。Web应用和Java企业级应用中,应用服务器经常要使用复杂的类加载器结构来实现JNDI(Java命名和目录接口)、
线程池、组件热部署等功能,因此理解这一点尤其重要。
为什么要引入线程的上下文类加载器?将它引入J2SE并不是纯粹的噱头,由于Sun没有提供充分的文档解释说明这一点,这使许多开发者很糊涂。实际上,上
下文类加载器为同样在J2SE中引入的类加载代理机制提供了后门。通常JVM中的类加载器是按照层次结构组织的,目的是每个类加载器(除了启动整个JVM
的原初类加载器)都有一个父类加载器。当类加载请求到来时,类加载器通常首先将请求代理给父类加载器。只有当父类加载器失败后,它才试图按照自己的算法查
找并定义当前类。
有时这种模式并不能总是奏效。这通常发生在JVM核心代码必须动态加载由应用程序动态提供的资源时。拿JNDI为例,它的
核心是由JRE核心类(rt.jar)实现的。但这些核心JNDI类必须能加载由第三方厂商提供的JNDI实现。这种情况下调用父类加载器(原初类加载
器)来加载只有其子类加载器可见的类,这种代理机制就会失效。解决办法就是让核心JNDI类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆
着代理机制的方向使用类加载器。(bad translation, cuiyi add)
顺便提一下,XML解析API(JAXP)也是使用此种机制。当JAXP还是J2SE扩展时,XML解析器使用当前累加载器方法来加载解析器实现。但当JAXP成为J2SE核心代码后,类加载机制就换成了使用线程上下文加载器,这和JNDI的原因相似。
好了,现在我们明白了问题的关键:这两种选择不可能适应所有情况。一些人认为线程上下文类加载器应成为新的标准。但这在不同JVM线程共享数据来沟通时,
就会使类加载器的结构乱七八糟。除非所有线程都使用同一个上下文类加载器。而且,使用当前类加载器已成为缺省规则,它们广泛应用在类声明、
Class.forName等情景中。即使你想尽可能只使用上下文类加载器,总是有这样那样的代码不是你所能控制的。这些代码都使用代理到当前类加载器的
模式。混杂使用代理模式是很危险的。
更为糟糕的是,某些应用服务器将当前类加载器和上下文类加器分别设置成不同的ClassLoader实例。虽然它们拥有相同的类路径,但是它们之间并不存
在父子代理关系。想想这为什么可怕:记住加载并定义某个类的类加载器是虚拟机内部标识该类的组成部分,如果当前类加载器加载类X并接着执行它,如JNDI
查找类型为Y的数据,上下文类加载器能够加载并定义Y,这个Y的定义和当前类加载器加载的相同名称的类就不是同一个,使用隐式类型转换就会造成异常。
这种混乱的状况还将在Java中存在很长时间。在J2SE中还包括以下的功能使用不同的类加载器:
* JNDI使用线程上下文类加载器
*
Class.getResource()和Class.forName()使用当前类加载器
* JAXP使用上下文类加载器
* java.util.ResourceBundle使用调用者的当前类加载器
*
URL协议处理器使用java.protocol.handler.pkgs系统属性并只使用系统类加载器。
* Java序列化API缺省使用调用者当前的类加载器
这些类加载器非常混乱,没有在J2SE文档中给以清晰明确的说明。
分享到:
相关推荐
使用getContextClassLoader().getResourceAsStream()可以自动管理文件路径,但需要使用Thread.currentThread().getContextClassLoader()方法来获取ClassLoader对象。希望本文能够对大家的学习和工作产生一定的参考...
最后,如果需要获取JAR文件所在的路径(尤其是当程序被打包成JAR时),可以使用`java.lang.instrument.Instrumentation`接口,但这个通常需要在启动时传递特殊参数 `-javaagent` 来启用。不过,在一般应用中,`...
String krbStr=Thread.currentThread().getContextClassLoader().getResource("krb").getFile(); //获取用户票据hezhong路径(hezhong为给合众分配的用户配置文件) String keyStr=Thread.currentThread()...
例如,在JDBC编程中,我们需要加载JDBC驱动程序时,可能需要使用Thread.currentThread().getContextClassLoader()获取的类加载器来加载驱动程序的类。这样可以确保正确地加载驱动程序的类。 Java中的类加载器具有...
`Thread.getContextClassLoader()`返回当前线程的上下文类加载器,可以使用`Thread.setContextClassLoader(ClassLoader cl)`来设置。上下文类加载器主要用于类的加载,尤其是在使用动态加载和插件系统时。 6. **...
JDK版本: 1.8.0_131 错误说明: 当构造来获取Nashorn引擎时,发送给构造函数的ClassLoader不会用于构造... * 3: invokevirtual java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader; * 6: astore_0
这段代码首先通过`Thread.currentThread().getContextClassLoader().getResource()`或`getResourceAsStream()`方法找到类路径下的资源,然后使用IOUtils的`toString()`方法将输入流转换为字符串,这样就能方便地读取...
这里,`Thread.currentThread().getContextClassLoader()`获取当前线程的类加载器,`getResourceAsStream()`方法用于找到资源并返回一个输入流。 2. **使用`ServletContext`** 在Servlet环境中,可以使用`...
InputStream inStream = Thread.currentThread() .getContextClassLoader().getResourceAsStream( "application.properties"); properties.load(inStream); dataSource = BasicDataSourceFactory....
2. **使用`Thread.currentThread().getContextClassLoader().getResource("")`**: - 获取当前线程上下文类加载器的资源路径。 - 示例代码: ```java System.out.println(Thread.currentThread()....
Java路径中的空格问题 ... Thread.currentThread().getContextClassLoader().getResource().getPath();等多种相似方式获得的路径,不能被FileReader()和FileWriter()直接应用,原因是URL对空格,特
例如,`Thread.currentThread().getContextClassLoader().getResourceAsStream("filename")`可以找到并打开`src`目录下的文件。这种方法允许你在打包后的JAR或WAR文件中正确地访问资源。 6. **处理资源路径**: - ...
推荐使用`Thread.currentThread().getContextClassLoader().getResource("")`来获取当前classpath的绝对路径的URI表示。 在处理Web应用中的路径时,`HttpServletRequest`对象的`getRealPath("/")`或`getRealPath("/...
你可以通过`Thread.currentThread().getContextClassLoader()`或`Class.getResourceAsStream()`来获取`ClassLoader`实例,然后使用`getResourceAsStream()`方法加载资源文件。例如: ```java InputStream in = ...
在上述代码中,我们使用了`Thread.currentThread().getContextClassLoader()`来获取当前线程的类加载器,然后通过`getResourceAsStream()`方法找到并读取配置文件。这种方法适用于动态加载的Web环境,因为它会根据...
- `Thread.currentThread().getContextClassLoader().getResource("/relative/path")`:获取当前线程上下文类加载器下的相对路径。 - 这种方式也适用于获取类路径下的资源。 - **通过系统属性获取路径**: - `...
1. 创建`URLClassLoader`对象:使用`URL[] urls`数组和父类加载器(通常是`Thread.currentThread().getContextClassLoader()`)构造一个新的`URLClassLoader`实例。 ```java URL[] classPathUrls = { new URL("file...
此外,我们还可以使用 `Thread.currentThread().getContextClassLoader().getResource("")` 来获取当前类的相对路径。 效劳器中的 Java 类获得当前路径 在效劳器中的 Java 类中,我们可以使用 `WebApplication 的...
问题是当从@onOpen 方法和@onMessage 方法调用代码时, Thread.currentThread().getContextClassLoader() 是不同的。 在 onMessage 中,上下文类加载器将是 org.codehaus.plexus.classworlds.realm.ClassRealm 而...
System.out.println(Thread.currentThread().getContextClassLoader().getResource("").getPath()); // 输出当前类加载器的路径 System.out.println(Thread.currentThread().getContextClassLoader().getResource...