`
退役的龙弟弟
  • 浏览: 446826 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java动态加载jar为什么使用Thread.getContextClassLoader()

 
阅读更多

这个问题经常出现在编写框架代码 需要动态加载很多类和资源的时候 通常当你需要动态加载资源的时候 你至少有三个 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)作为自己的线程上下文类加载器.自从WebJ2EE应用服务器使用成熟的类加载器机制来实现诸如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中存在一段时间对于那些需要动态加载资源的J2SEAPI, 我们来猜想它们的类加策略例如:

Ø         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);
		}
	}
}

 

分享到:
评论

相关推荐

    Hbase 基本操作类

    String krbStr=Thread.currentThread().getContextClassLoader().getResource("krb").getFile(); //获取用户票据hezhong路径(hezhong为给合众分配的用户配置文件) String keyStr=Thread.currentThread()...

    JDK8-Nashorn-BUG:Nashorn中的错误

    JDK版本: 1.8.0_131 错误说明: 当构造来获取Nashorn引擎时,发送给构造函数的ClassLoader不会用于构造... * 3: invokevirtual java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader; * 6: astore_0

    day020-继承加强和设计模式代码和笔记.rar

    输入 :java -jar jar包名.jar step2:剩下的就是按照 3.1中的步骤使用即可 4. Properties资源(配置)文件的解析(重点) ---------------------------------(注意:重点)----------------...

    JettyWebSocketTest2

    问题是当从@onOpen 方法和@onMessage 方法调用代码时, Thread.currentThread().getContextClassLoader() 是不同的。 在 onMessage 中,上下文类加载器将是 org.codehaus.plexus.classworlds.realm.ClassRealm 而...

    Java路径问题解决方案汇集

    Java路径中的空格问题 ...  Thread.currentThread().getContextClassLoader().getResource().getPath();等多种相似方式获得的路径,不能被FileReader()和FileWriter()直接应用,原因是URL对空格,特

    jdbc 资料大全

    InputStream inStream = Thread.currentThread() .getContextClassLoader().getResourceAsStream( "application.properties"); properties.load(inStream); dataSource = BasicDataSourceFactory....

    xml与反射.txt

    第一:获取类加载器:ClassLoader loader=Thread.currentThread().getContextClassLoader();//获取当 前线程的上下文类加载器 第二:通过类加载器获取类 Class clazz=loader.loadClass("com.taobao.reflect.car")//...

    Exception in thread “main” javax.imageio.IIOException: Can’t read input file!

    Exception in thread “main” javax.imageio.IIOException: Can’t read input file!... private static String basePath=Thread.currentThread().getContextClassLoader().getResource().getPath(); public static v

    大数据面试题.pdf

    ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以⽤(ClassLoader.getSystemClassLoader()) Class cls = loader.loadClass("xx.xx"); //这句话没有执⾏初始化 forName可以控制是否初始...

    对Java的ClassLoader的几点认识

    ClassLoader与其他类的不同之处是它是为JVM服务的,属于“公务员”,例如Thread带有getContextClassLoader()和setContextClassLoader()方法;  2、ClassLoader的实现类URLClassLoader完成工作的方法是根据传入的...

Global site tag (gtag.js) - Google Analytics