Java 运行Jar包 Classpath问题

在java中,运行一个应用程序很简单,只要

java XXX

即可将程序跑起来。如果复杂一点,可能你的程序会依赖某些第三方库或者自己实现的库,你只需要
java -classpath(-cp) .:a.jar:b.jar app (unix)
java -classpath(-cp) .;a.jar;b.jar app (windows)

为什么要指定classpath呢?因为不指定classpath虚拟机去哪里找你想要依赖的库呀,所以不指定classpath, 编译器一定会给你一个ClassNotFound的Error。
当你更加深入了解Java技术,开始做一些企业级的项目的时候,你会发现老是直接到一个目录下运行一个class文件显得十分低端,没有档次。于是开始构建属于自己的库,将这个应用程序打成一个jar包,并且写一点自己的脚本语言,然后形成一个高端大气上档次的应用包,这个包里面会有:

MyApp

  • bin

-----startup.sh

  • lib

-----app.jar

-----a.jar

-----b.jar

  • conf

-----conf.xml

  • license ( 这个很酷)

然后在startup.sh脚本中,你开始将classpath构建好,然后使用

java -classpath .:a.jar:b.jar -jar app

将程序运行起来。一切看起来都是那么的顺利。
但是很不幸,程序意外地给了你一个错误,一个让你很无语的错误:

Exception in thread “main” java.lang.NoClassDefFoundError: com/example/a/A
at com.example.app.App.main(Server.java:14)
Caused by: java.lang.ClassNotFoundException: com.zhuowenfeng.util.StringUtil
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader 1. r u n ( U R L C l a s s L o a d e r . j a v a : 355 ) a t j a v a . s e c u r i t y . A c c e s s C o n t r o l l e r . d o P r i v i l e g e d ( N a t i v e M e t h o d ) a t j a v a . n e t . U R L C l a s s L o a d e r . f i n d C l a s s ( U R L C l a s s L o a d e r . j a v a : 354 ) a t j a v a . l a n g . C l a s s L o a d e r . l o a d C l a s s ( C l a s s L o a d e r . j a v a : 423 ) a t s u n . m i s c . L a u n c h e r 1.run(URLClassLoader.java:355) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:354) at java.lang.ClassLoader.loadClass(ClassLoader.java:423) at sun.misc.Launcher 1.run(URLClassLoader.java:355)atjava.security.AccessController.doPrivileged(NativeMethod)atjava.net.URLClassLoader.findClass(URLClassLoader.java:354)atjava.lang.ClassLoader.loadClass(ClassLoader.java:423)atsun.misc.LauncherAppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
… 1 more
你无语是因为你检查了n遍脚本输出,看到所有的依赖都已经加到classpath里面去了,这怎么可能会出现这个错误,有点颠覆你的世界观了。

这时候我需要告诉你,这里的java参数-classpath根本没有起到任何作用,换句话说,你辛辛苦苦用脚本构建出来的classpath在这里一点作用都没有。

Java在运行可执行的jar包时,它会忽略你设置的classpath以及所有环境变量,然后编译器只会以你的jar包为根路径去找class文件。这时候自然而然的蹦出了一个疑问,为什么要这样呢?甚至还可能爆一句粗口,程序员最喜欢用的词,字母F开头的。

sun的人(现在应该叫oracle了,习惯说sun)难道不会想到这点吗?我其实也在探究这个问题,有一些猜想,你可以试着去判断对错。

假设在运行可执行jar包时,-classpath是有用的,可以开开心心的加入很多依赖库,很方便省事。试想,现在你的应用程序app.jar里面的class文件是这样的:

app.jar

  • com

— example

------ App.class

然后,你也写了另一个库util.jar,作为app.jar的一个依赖库,提供一些实用类什么的,它的结构是这样的:

util.jar

  • com

— example

------ App.class

图个方便很多jar包都这么一个结构,或许一个第三方库,它也有这么一个类,也是这么一个结构。你想想,当java运行起来的时候,还是你想要运行的那段代码吗?要知道java的classloader很傲娇,加载了一个类,就不会去加载第二个同名(全名)的类了。所以说开发java的人也是有苦衷的。

明白了道理,是时候想想该怎么解决这个问题。

一、在java里,提供了一个启动参数的设置:

-Xbootclasspath
当你使用这个参数来设置你的classpath的时候,你发现能够成功运行你的jar包了,皆大欢喜。这个参数指定的classpath中的类,都会使用JVM 的 Boot Classloader来进行加载,这个classloader是JVM中所有classloader的老大,因此它不是由java实现的,而是使用C++/C来实现的。因此当你运行你的程序的时候,你的依赖类都会被load的。小心不要犯上面举的这个例子就好。

当然事情还没有完。

二、jar包中的Manifest.mf文件

当你开始使用一些框架来构建的应用程序时,你对java又有了更深一步的认识。比如你开始使用spring了。这是一个让你欲罢不能的框架,几乎是无所不能。spring的Annotation无所不在,也无所不能,利用注解这个有利工具,spring可以做到AOP,可以做到依赖注入,可以做到各种对象的初始化。你会好奇它是怎么实现的呢?我想其中有一个很重要的角色–Classloader。

正是因为classloader,spring才能轻轻松松的使用注解来管理应用程序。那么这个跟我们这篇文章有什么关系呢?关系大着。

当你使用spring构建了应用程序,也像上面的App一样进行了打包,构建脚本。然后使用-xbootclasspath指定了路径。你觉得万无一失,然后运行了脚本,可是程序却扔给了你一个错误:

bean cannot be initialized。。。

我觉得你应该已经知道了原因。对,没错,就是因为-Xbootclasspath提前把你的依赖类load进了JVM,才让你的spring的classloader再也不能去初始化已经用Annotation声明过的Component,Controller and so on。是不是接近崩溃了。

但是,任何事情都是有解决办法的。jar包它提供了一个Manifest.mf文件,用来描述这个jar包的属性等信息。比如Main-class,比如Class-Path,对!就是这个Class-Path能够解决以上所有问题,让你不再烦恼jar包的classpath。你只要在Manifest.mf中指定你所依赖的jar包名称,然后将这些jar包置于应用程序jar包的同一层目录或者子目录,然后就轻轻松松的可以运行程序啦。

Class-Path: a.jar b.jar lib/c.jar

至此我想说的就到这里了。
三、其实还有一种很silly的方法,将依赖包copy到{Java_home}\jre\lib\ext目录下,然后使用系统的extension classloader来加载这些类。

你会这么做吗?估计不会。
————————————————
版权声明:本文为CSDN博主「wenfengzhuo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wenfengzhuo/article/details/10741825

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
如果你在打成 jar 包时,配置的 xml 文件路径在 classpath 下,但是在运行时出现了找不到文件的错误,可以尝试以下几个步骤: 1. 确认 classpath 是否正确 在打包时,需要将 xml 文件配置到 classpath 中,可以在 MANIFEST.MF 文件中添加 Class-Path 配置项,指定 xml 文件所在的相对路径,例如: ``` Class-Path: config/ ``` 这样,在运行 jar 包时,JVM 会自动将 config 目录下的文件添加到 classpath 中。需要注意的是,Class-Path 的路径是相对于 jar 包所在的目录的。 2. 确认 xml 文件路径是否正确 在加载 xml 文件时,需要使用相对路径或绝对路径,如果使用相对路径,需要相对于 classpath 的根目录,例如: ```java InputStream is = getClass().getResourceAsStream("/config/config.xml"); ``` 这里假设 xml 文件在 jar 包的 config 目录下。需要注意的是,路径前面需要加上 "/",表示相对于 classpath 的根目录。 如果使用绝对路径,需要使用 File 类的构造函数,例如: ```java File configFile = new File("config/config.xml"); InputStream is = new FileInputStream(configFile); ``` 这里假设 xml 文件在 jar 包的同级目录下的 config 目录下。 3. 确认 xml 文件是否被打包进 jar 包 在打包时,需要将 xml 文件打包进 jar 包中,可以使用 Maven 插件或者 Gradle 插件来完成。如果手动打包,需要确保 xml 文件被正确地添加到 jar 包中。 如果以上步骤都没有解决问题,可以尝试在运行时输出 classpath,查看 xml 文件是否被正确地添加到 classpath 中。例如: ```java String classpath = System.getProperty("java.class.path"); System.out.println(classpath); ``` 希望以上步骤能够帮助你解决问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值