我能使用classpath来覆盖正在运行的jar文件中的文件吗?

22

我有一个包含应用程序以及该应用程序的配置文件的JAR文件。该应用程序使用ClassLoader.getResource()从类路径加载配置文件,并使用打包在JAR文件中的配置文件完全满足其依赖项。

偶尔,我希望使用稍微不同的配置运行应用程序(具体来说,我想要覆盖JDBC URL以指向不同的数据库),因此我创建了一个新的配置文件,将其存储在正确的目录结构中(也就是一个类路径条目的/config目录中),然后我想做这样的事情:

java -cp new-config:. -jar application.jar

但是我无法让classpath在应用程序JAR内容之前具有new-config路径条目。 JAR的内容总是第一件事,这是硬编码的吗?


你尝试过将配置文件放在应用程序jar包外面,放在一个相对于application.jar的路径下的自己的jar文件中吗(../conf/config.jar)?如果这样做,我认为你可以在应用程序清单中设置classpath指向该配置jar,并且你可以通过更改config.jar来设置新的配置。我希望有更多时间制作演示以确认我的答案,但我不能...所以我把它写成了评论。 - JuanZe
你的意思是,不是在JAR包里面? - Guss
是的,与其将配置文件放在应用程序的同一个jar包中,不如将其放在第二个jar包中... - JuanZe
谢谢,但这对我的使用并不适用 - 我试图支持的用户使用构建脚本在他们的工作站上创建JAR文件,然后手动将其传输到服务器。要求他们传输两个文件会有问题。 - Guss
已知一个尚未修复的错误是不能同时使用-cp和-jar的:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4459663 - Wolfgang Fahl
4个回答

24

为什么不直接调用应用程序而不指定-jar,而是显式命名应用程序的主类?这将允许您按所需顺序将新配置和application.jar都放在类路径上:

例如(假设“new-config”是包含被覆盖属性文件的目录)

java -cp new-config:application.jar Application.Main.Class

我认为主类的名称可以在JAR文件内的MANIFEST.MF文件中找到....


2
主要问题在于我实际上并没有使用-cp参数,而是在清单文件中指定了类路径,因为该应用程序需要许多其他外部JAR文件 - 提取所有这些将会相当容易出错。但我敢打赌我可以编写一些脚本从清单中提取类路径并自动构建相关的命令行,所以这可能是我要使用的答案。 - Guss

15

当您使用-jar选项启动应用程序时:

... JAR文件是所有用户类的来源,而其他用户类路径设置将被忽略

此处所述。解决方法是在jar文件的清单中指定类路径,以包括附加路径(如此处所述)。

然而,考虑到您只谈论修改配置,您可能希望采取不依赖于类路径的不同方法。例如,我通常使用Spring通过属性文件配置我的应用程序来确定数据库等位置。我的Spring配置在测试、QA和生产环境下保持一致,但我在启动应用程序时传递不同的属性文件作为命令行参数。

Spring配置片段

<bean id="MyDataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
    <property name="url" value="jdbc:microsoft:sqlserver://${dbServer}:${dbPort};DatabaseName=${dbName}"/>
    <property name="username" value="${dbUserName}"/>
    <property name="password" value="${dbPassword}"/>
    <property name="suppressClose" value="false"/>
</bean>

属性文件片段

dbServer=MyServer
dbPort=1433
dbName=MyDb
dbUserName=Me
dbPassword=foobar

1
将配置文件作为可选参数传递是个好主意,但我的应用程序目前接受相当多的参数,再添加一个参数会给用户带来问题。不过还是谢谢你的回答。 - Guss
你如何启动你的应用程序?我认为通过Webstart、.bat或.sh脚本等方式启动应用程序时,传递的参数应该对用户隐藏。 - Adamski
不,用户需要从命令行自己启动应用程序,并传入所需的参数 - 日期、要处理的文件等。 - Guss
我猜你可以让用户通过一个脚本或批处理文件启动应用程序,该文件接受JVM参数或参数,并将它们与属性文件一起传递给JVM?如果不了解更多关于用户的上下文信息,很难确定这是否是最佳选项。 - Adamski
最终,我们选择了更复杂的实现方式,即在回退到类路径加载语义之前,将加载器逻辑首先查找当前工作目录下的几个文件夹。 - Guss

3

-jar选项指定的JAR存档文件将覆盖所有其他值。

通常情况下,您需要使用外部配置文件或使用ClassLoader.getResource()构建自己的解决方案。

我们使用自定义解决方案来解决此问题 - 我们这样加载内部属性:

final Properties p = new Properties();
p.load(DefaultConfiguration.class.getResourceAsStream("config.properties"));

我们可以通过相同的方式加载外部文件,并用外部文件中的值覆盖内部值。
有关类加载的信息,请参见: http://java.sun.com/javase/6/docs/technotes/tools/findingclasses.html

这确实是个问题。我明白除了按照alasdairg的建议以不同的方式启动应用程序或编写一些自定义加载代码之外,没有其他解决方法。谢谢。 - Guss

1

仅使用CLASSPATH可能不可行。有方法可以使调用ClassLoader.getResource()使用静态路径来查找资源。如果是这样做的,它将绕过CLASSPATH。


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