侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

轻松玩转Java配置的Classpath

Java  /  管理员 发布于 8年前   156

  和Java类路径(classpath)打交道的过程中,开发者偶尔会遇到麻烦。这是因为,类装载器实际装入的是哪一个类有时并不显而易见,当应用程序的classpath包含大量的类和目录时,情况尤其严重。本文将提供一个工具,它能够显示出被装入类文件的绝对路径名。

  一、Classpath基础

  Java虚拟机(JVM)借助类装载器装入应用程序使用的类,具体装入哪些类根据当时的需要决定。CLASSPATH环境变量告诉类装载器到哪里去寻找第三方提供的类和用户定义的类。另外,你也可以使用JVM命令行参数-classpath分别为应用程序指定类路径,在-classpath中指定的类路径覆盖CLASSPATH环境变量中指定的值。

  类路径中的内容可以是:文件的目录(包含不在包里面的类),包的根目录(包含已打包的类),包含类的档案文件(比如.zip文件或者.jar文件)。在Unix家族的系统上,类路径的各个项目由冒号分隔,在MS Windows系统上,它们由分号分隔。

  类装载器以委托层次的形式组织,每一个类装载器有一个父类装载器。当一个类装载器被要求装载某个类时,它在尝试自己寻找类之前会把请求先委托给它的父类装载器。系统类装载器,即由安装在系统上的JDK或JRE提供的默认类装载器,通过CLASSPATH环境变量或者-classpath这个JVM命令行参数装入第三方提供的类或者用户定义的类。系统类装载器委托扩展类装载器装入使用Java Extension机制的类。扩展类装载器委托自举类装载器(bootstrap class loader)装入核心JDK类。

  你可以自己开发特殊的类装载器,定制JVM如何动态地装入类。例如,大多数Servlet引擎使用定制的类装载器,动态地装入那些在classpath指定的目录内发生变化的类。

  必须特别注意的是(也是令人吃惊的是),类装载器装入类的次序就是类在classpath中出现的次序。类装载器从classpath的第一项开始,依次检查每一个设定的目录和压缩文件,尝试找出待装入的类文件。当类装载器第一次找到具有指定名字的类时,它就把该类装入,classpath中所有余下的项目都被忽略。

  看起来很简单,对吧?

  二、可能出现的问题

  不管他们是否愿意承认,初学者和富有经验的Java开发者都一样,他们都曾经在某些时候(通常是在那些最糟糕的情形下)被冗长、复杂的classpath欺骗。应用程序所依赖的第三方类和用户定义类的数量逐渐增长,classpath也逐渐成了一个堆积所有可能的目录和档案文件名的地方。此时,类装载器首先装载的究竟是哪一个类也就不再显而易见。如果classpath中包含重复的类入口,这个问题尤其突出。前面已经提到,类装载器总是装载第一个它在classpath中找到的具有合适名字的类,从实际效果看,它“隐藏”了其他具有合适名字但在classpath中优先级较低的类。

  如果不小心,你很容易掉进这个classpath的陷阱。当你结束了一天漫长的工作,最后为了让应用程序使用最好、最新的类,你把一个目录加入到了classpath,但与此同时,你却忘记了:在classpath的另一个具有更高优先级的目录下,存放着该类的另一个版本!

   三、一个简单的classpath工具

  优先级问题是扁平路径声明方法与生俱来固有的问题,但它不是只有Java的classpath才有的问题。要解决这个问题,你只需站到富有传奇色彩的软件巨构的肩膀上:Unix操作系统有一个which命令,在命令参数中指定一个名字,which就会显示出当这个名字作为命令执行时执行文件的路径名。实际上,which命令是分析PATH变量,然后找出命令第一次出现的位置。对于Java的类路径管理来说,这应该也是一个好工具。在它的启发之下,我着手设计了一个Java工具JWhich。这个工具要求指定一个Java类的名字,然后根据classpath的指引,找出类装载器即将装载的类所在位置的绝对路径。

  下面是一个JWhich的使用实例。它显示出当Java类装载器装载com.clarkware.ejb.ShoppingCartBean类时,该类第一次出现位置的绝对路径名,查找结果显示该类在某个目录下:

  > java JWhich com.clarkware.ejb.ShoppingCartBean

  Class 'com.clarkware.ejb.ShoppingCartBean' found in
  '/home/mclark/classes/com/clarkware/ejb/ShoppingCartBean.class'

  下面是第二个JWhich的使用实例。它显示出当Java类装载器装载javax.servlet.http.HttpServlet类时,该类第一次出现位置的绝对路径名,查找结果显示该类在某个档案文件中:

  > java JWhich javax.servlet.http.HttpServlet

  Class 'javax.servlet.http.HttpServlet' found in
  'file:/home/mclark/lib/servlet.jar!/javax/servlet/http/HttpServlet.class'

  四、JWhich的工作过程

  要精确地测定classpath中哪一个类先被装载,你必须深入到类装载器的思考方法。事实上,具体实现的时候并没有听起来这么复杂――你只需直接询问类装载器就可以了!

  1: public class JWhich {
  2:
  3: /**
  4: * 根据当前的classpath设置,
  5: * 显示出包含指定类的类文件所在
  6: * 位置的绝对路径
  7: *
  8: * @param className <类的名字>
  9: */
  10: public static void which(String className) {
  11:
  12: if (!className.startsWith("/")) {
  13: className = "/" + className;
  14: }
  15: className = className.replace('.', '/');
  16: className = className + ".class";
  17:
  18: java.net.URL classUrl =
  19: new JWhich().getClass().getResource(className);
  20:
  21: if (classUrl != null) {
  22: System.out.println(" Class '" + className +
  23: "' found in '" + classUrl.getFile() + "'");
  24: } else {
  25: System.out.println(" Class '" + className +
  26: "' not found in '" +
  27: System.getProperty("java.class.path") + "'");
  28: }
  29: }
  30:
  31: public static void main(String args[]) {
  32: if (args.length > 0) {
  33: JWhich.which(args[0]);
  34: } else {
  35: System.err.println("Usage: java JWhich ");
  36: }
  37: }
  38: }

  首先,你必须稍微调整一下类的名字以便类装载器能够接受(12-16行)。在类的名字前面加上一个“/”表示要求类装载器对classpath中的类名字进行逐字精确匹配,而不是尝试隐含地加上调用类的包名字前缀。把所有“.”转换为“/”的目的是,按照类装载器的要求,把类名字格式化成一个合法的URL资源名。

  接下来,程序向类装载器查询资源,这个资源的名字必须和经过适当格式化的类名字匹配(18-19行)。每一个Class对象维护着一个对装载它的ClassLoader对象的引用,所以这里是向装载JWhich类的类装载器查询。Class.getResource()方法实际上委托装入该类的类装载器,返回一个用于读取类文件资源的URL;或者,当指定的类名字不能在当前的classpath中找到时,Class.getResource()方法返回null。

  最后,如果当前的classpath中能够找到指定的类,则程序显示包含该类的类文件所在位置的绝对路径名(21-24行)。作为一种调试辅助手段,如果当前classpath中不能找到指定的类,则程序获取java.class.path系统属性并显示当前的classpath(24-28行)。

  很容易想象,在使用Servlet引擎classpath的Java Servlet中,或者在使用EJB服务器classpath的EJB组件中,上面这段简单的代码是如何运作。例如,如果JWhich类是由Servlet引擎的定制类装载器装入,那么程序将用Servlet引擎的类装载器去寻找指定的类。如果Servlet引擎的类装载器不能找到类文件,它将委托它的父类装载器。一般地,当JWhich被某个类装载器装入时,它能够找出当前类装载器以及所有其父类装载器所装入的所有类。

  【结束语】

  如果需要是所有发明之母,那么帮助我们管理Java类路径的工具可以说迟到了很长时间。Java新闻组和邮件列表中充塞着许多有关classpath的问题,现在JWhich为我们提供了一个简单却强大的工具,帮助我们在任何环境中彻底玩转Java类路径。


  • 上一条:
    为Java应用程序添加退出事件响应
    下一条:
    Java新手入门的30个基本概念
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在java中实现的脱敏工具类代码示例分享(0个评论)
    • zookeeper安装流程步骤(0个评论)
    • 在java中你背的“八股文”可能已经过时了(2个评论)
    • 在php8.0+版本中使用属性来增加值代码示例(3个评论)
    • java 正则表达式基础,实例学习资料收集大全 原创(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-11
    • 2018-03
    • 2020-03
    • 2023-05
    • 2023-11
    • 2024-01
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客