可执行的jar文件找不到属性文件

16

我在程序中使用下面的代码加载一个属性文件:

Properties properties = new Properties();
URL url = new App().getClass().getResource(PROPERTIES_FILE);
properties.load(url.openStream());

代码在Eclipse中运行良好。然后我将程序打包成了一个名为MyProgram.jar的JAR文件,并运行它,在第二行代码处遇到了NullPointerException异常。JAR文件不包含属性文件,它们都在同一个目录下。我正在使用Maven创建这个JAR文件。如何解决这个问题?

更新:我不想将属性文件添加到JAR文件中,因为它将在部署时创建。


2
旁注:你使用 getResource() 而不是 getResourceAsStream() 有什么原因吗?因为你只是在使用流而已。 - Powerlord
1
不,我没有任何特别的原因。 - user168237
@Tomer:那只是一个解决方法,而不是解决方案。仍然无法工作。您没有在类路径中考虑JAR的根文件夹。 - BalusC
@Hai:你为什么删除了maven标签?你在我的回答上评论说“你知道如何通过maven添加它吗?”所以你正在使用maven,由于它的操作方式不同,最终答案取决于它。我不知道怎么做,所以我删除了答案并重新标记问题以便更好地关注。Maven的人是唯一可以可靠回答这个问题的人。 - BalusC
@BalusC:这只是一个诊断步骤,而不是解决方案(因此是评论而不是答案 :-))。 - Tomer Gabel
显示剩余5条评论
5个回答

22

BalusC是正确的,您需要指示Maven在 Class-Path: 条目中生成当前目录(.)的 MANIFEST.MF

假设您仍在使用Maven Assembly插件和 jar-with-dependencies 描述符来构建可执行JAR,则可以使用以下命令告诉插件这样做:

  <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.2</version>
    <configuration>
      <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
      <archive>
        <manifest>
          <mainClass>com.stackoverflow.App</mainClass>
        </manifest>
        <manifestEntries>
          <Class-Path>.</Class-Path> <!-- HERE IS THE IMPORTANT BIT -->
        </manifestEntries>
      </archive>
    </configuration>
    <executions>
      <execution>
        <id>make-assembly</id> <!-- this is used for inheritance merges -->
        <phase>package</phase> <!-- append to the packaging phase. -->
        <goals>
          <goal>single</goal> <!-- goals == mojos -->
        </goals>
      </execution>
    </executions>
  </plugin>

1
如果您没有使用Maven Assembly插件,请告诉我,我会更新我的答案。 - Pascal Thivent
通过executions部分,我不需要单独运行mvn assembly:assembly对吧?顺便说一下,感谢您在Maven方面回答了我所有的问题。 - user168237
1
@HaiMinhNguyen:确实,上述的executionsingle目标绑定到了package阶段。因此,运行mvn package确实会创建汇编文件。哦,不用谢。 - Pascal Thivent
是的,这是你应该采取的方法,Hai Minh Nguyen。 - BalusC
7
你好,我尝试使用上述方法创建一个包含依赖项的jar文件,该文件可以加载位于同一目录中的属性文件,但是它不起作用。我使用"java -jar myjar.jar"运行我的jar文件。我尝试使用Class.getResource()和Class.ClassLoader.getResource()两种方法来检索属性文件,其中包括文件名前面是否有斜杠,但这些方法都不起作用。如果我打印java.class.path的内容,我只得到了我的jar文件(没有“.”,尽管在MANIFEST的Class-Path中已正确设置)。有人能帮我吗? - Bastien Jansen
这个线程对我很有帮助。在我的情况下(非maven项目),我还需要通过在请求的资源路径前加上“/”(Linux)来将我所请求的资源路径(嵌套在我的src树中)变成绝对路径。 - jgreen

18

有两个解决方法

  1. 不要将JAR文件作为可执行的JAR文件使用,而是将其作为库使用。

    java -cp .;filename.jar com.example.YourClassWithMain
    
  2. 获取JAR文件的根目录位置并从中获取属性文件。

  3. URL root = getClass().getProtectionDomain().getCodeSource().getLocation();
    URL propertiesFile = new URL(root, "filename.properties");
    Properties properties = new Properties();
    properties.load(propertiesFile.openStream());
    
    None of both are recommended approaches! The recommend approach is to have the following entry in JAR's /META-INF/MANIFEST.MF file:
    Class-Path: .
    

    然后它将作为类路径资源以通常的方式可用。您确实需要以某种方式指示Maven生成像这样的MANIFEST.MF文件。


哎呀,我之前尝试过第一种选项,但是我使用了“逗号”文件名.jar。也许这就是为什么它没有起作用的原因。谢谢你的帮助。 - user168237
避免使用Classpath的一个原因是如果读取之间内容发生更改。资源加载器会缓存第一次读取的内容。 - Thorbjørn Ravn Andersen

1

编辑:这是回应您的评论:

您需要确保属性文件在类路径上,并且具有启动jar文件的java调用的正确根目录。如果您的路径为

stuff/things.properties

而且运行时位置是

/opt/myapp/etc/stuff/things.properties

而且,jar文件在

/opt/myapp/bin/myjar

所以你需要这样启动

/path/to/java -cp "/opt/myapp/etc:/opt/myapp/bin/myjar.jar" my.pkg.KavaMain

在开发环境中使用这种配置可能会很繁琐,幸运的是,有maven exec插件可以为您提供正确的启动场景。

原始答案:

您想了解maven资源插件

基本上,您想添加类似于此的内容:

<plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
                <resources>
                        <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                        <include>**/*properties</include>
                                </includes>
                        </resource>
                </resources>
        </configuration>
<plugin>

假设你的属性文件与Java源代码在一起,那么将它添加到pom.xml中--实际上应该放在src/main/resources中。

谢谢,但我不想将属性文件添加到JAR中。它将在部署时更改。 - user168237
根据您的编辑:在使用“-jar”参数时,“-cp”参数将被忽略。它确实需要放在“MANIFEST.MF”文件中 :) - BalusC
@BalusC 好的,已经修复。如果它在运行时可能会移动,就不能放在 MANIFEST.MF 中。 - lscoughlin

0

我曾经遇到过类似的问题,这个帖子对我帮助很大!顺便说一下,我修改了我的Ant构建文件来生成MANIFEST,然后在打包服务器端代码时指定了该清单:

<!-- Create a custom MANIFEST.MF file, setting the classpath. -->
<delete file="${project.base.dir}/resources/MANIFEST.MF" failonerror="false"/>
<manifest file="${project.base.dir}/resources/MANIFEST.MF">
  <attribute name="Class-Path" value="." />
</manifest>

<!-- JAR the server-side code, using the custom manifest from above. -->
<jar destfile="services/${name}.aar" manifest="${project.base.dir}/resources/MANIFEST.MF">
[....]

-3
感谢所有这段代码为我解决了问题,使得我能够轻松地获取我们可运行的JAR文件位置的URL。所以我必须读取我的属性文件,将您的属性文件放在可运行的JAR文件位置中。
URL root = getClass().getProtectionDomain().getCodeSource().getLocation();
URL propertiesFile = new URL(root, "filename.properties");
Properties properties = new Properties();
properties.load(propertiesFile.openStream());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接