Java查找依赖jar的绝对路径

huangapple go评论142阅读模式
英文:

Java Find the absolute path of dependent jar

问题

我有一个Jar文件,它依赖于另一个项目的Jar。两者都是瘦身的Jar,位于同一位置。第一个Jar有一个清单文件,在其class-path属性中列出了第二个Jar。

在第一个Jar中,我正在使用Java的ProcessBuilder类将第二个Jar作为进程启动。为此,我需要第二个Jar的绝对路径。在第一个Jar中,我有一个名为XClient的类。
如果我执行 XClient.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
我会得到第一个Jar的绝对路径。然后我可以拆分并添加第二个Jar的名称(硬编码)以构建绝对路径。

在第二个Jar中,我有一个名为XServer的类。
如果我执行

 XServer.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();

会抛出异常。

我不确定我是否采取了正确的方法,但我的目标非常明确,我想获取依赖Jar的绝对路径。

请帮忙解决。

英文:

I have Jar file which dependency on another project jar. Both are thin jars and are at same location. 1st jar has manifest file which list second jar in its class-path property.

In 1st jar I am launching second jar as a process using ProcesBuilder class in java. To do so I need absolute path of second jar. In 1st jar i have class XClient
If I do XClient.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
i am getting absolute path of 1st jar. Then I can split and add the name of second jar(hard-coded) to build the absolute path

In second jar I have class XServer
If I do

 XServer .class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();

Its throws exception

I am not sure if I am doing the right approach but my goal is very clear I wanted to get the absolute path to the dependent jar.

Please help

答案1

得分: 1

我尝试使用相同的方法(但是使用 File file=new File(this.getClass().getProtectionDomain().getCodeSource().toUri()) 代替 getPath()),但这可能会以不同的方式失败:

  1. 当类位于 JAR 文件内部时,File 对象指向的是 JAR 文件而不是包含 JAR 文件的文件夹 - 因此需要 if(file.isFile()) file=file.getParentFile(); 来获取文件夹而不是 JAR 文件。
  2. 当 JAR 文件被其他非常规的 URLClassLoader 加载时(我最后尝试是在 1.8 版本中 - 我只知道自从 Jigsaw 后,主类加载器不再可以强制转换为 URLClassLoader),这可能会返回一些未指定的结果,甚至可能根本不起作用,因此实际行为取决于系统设置 - 这可能会使得在不受您控制的远程系统上使用时难以进行调试。
  3. UNC 路径(Windows 共享)本身就容易出错 - 在其之上添加另一层(Java)会引入许多潜在的其他问题,您必须进行测试和调试 - 这通常会导致您告诉客户如何使用以及如何设置,而不是按照 Java 原则设计代码:"编写一次,编译一次,到处运行"(顺便说一句:即使您"挂载"了网络共享,以便可以通过本地驱动器号访问它,这也会导致在尝试链接两台机器时出现问题,其中一台是另一台的克隆)。
  4. 如前面的评论所述:"它不起作用" 不是一个有用或有意义的描述 - 如果您收到错误消息(在这种情况下,如您提到的异常堆栈跟踪),请将其与产生它的代码一起发布(如果可以访问)。

我是如何解决我的问题的?我只是通过一个 Swing 的 JFileChooser 来询问用户选择目录/文件。是的,这并不是绝对可靠的方法,可能也不是最好的方法 - 但它在 Swing 仍然随着 SE JVM(而不是 FX)一起发布的情况下可行。
如果您想要找到一个路径,使用 Class.getResource(),让 Java 来处理,就像加密一样:不要自己处理。

除此之外:您提到的"用例"并不需要您尝试做的事情。您说服务器已经在类路径中 - 因此它在启动时被加载,您可以访问 XServer 类。最简单的方法是不要分叉另一个进程,而是在另一个线程中运行它。如果您知道哪个类有主方法(服务器.jar 的清单会告诉您),并且您可以在类路径中访问它,只需执行类似于以下的操作:

Thread serverThread = new Thread(new Runnable()
{
    public void run()
    {
        String[] args = Arrays.asList("required", "parameters");
        XServer.main(args);
    }
});
serverThread.start();

如果不需要参数,只需传递一个空的字符串数组。由于 main() 不应该抛出异常(至少不是已检查的异常),因此不应该需要异常处理。

在所有这些评论被投向我之前:是的,我非常清楚可能存在的问题,比如类路径问题(同一包中相同类名但不同版本)等等,与其试图找出绝对路径并启动一个分叉/子进程相比,这种方法可能更可行。
另外:启动另一个进程可能需要与其流进行交互(将所需的输入提供到子进程的输入流中,并读取子进程的输出流和错误流 - 否则分叉的进程可能会“挂起”,因为它在等待管道被清空。如果不是您自己的代码,您可能难以调试这种问题,除非您可以将分析器和调试器附加到其中,以找出为什么一切突然停止工作。

如果您真的想要(我认为没有任何要求强制您"必须"这样做),与客户端一起启动服务器,请使用操作系统级别的启动脚本,而不是使用 Java。

英文:

I tried to use the same approach (but used File file=new File(this.getClass().getProtectionDomain().getCodeSource().toUri()) instead of getPath()) but this can fail in different ways:

  1. when the class is inside a jar the File object points to the jar instead the folder the jar is in - so an if(file.isFile()) file=file.getParentFile(); is needed to get the directory instead of the jar file
  2. when the jar file is loaded by something other than the usual URLClassLoader (last time I tried was back in 1.8 - and I only know that since Jigsaw the main classloader can't be cast to an URLClassLoader anymore) this may will return some unspecified result, if at all, so actual behaviour depends on the very system setup - wich can make it difficult to debug when used on a remote system not under your control
  3. UNC paths (Windows shares) are error prone by themselfs - adding another layer on top of it (java) just add a lot of potential other pitfalls you all have to test and debug - wich often ends up you tell the client what to use and how to setup instead of design your code to follow the java principle: "write once, compile once, run everywhere" (btw: this also applies even if you "mount" a network share so you can address it by a local drive letter instead of a remote network path - but this even causes problems when you try to link two machines where one is a clone of the other)
  4. as already mentioned as comment: "it doesn'T work" is not a usefull or meaningfull description - if you get an error message (in this case as you mentioned an exception stacktrace) post it along with the code wich produced it (if accessible)

How I solved my problem? I just ask the user for the directory / file by a swing JFileChooser. Yes, this isn't fool proof and maybe not the best way - but it works as swing still ships with SE JVM (instead of FX).
If you want to find a path use Class.getResource() and let java do the work, pretty much like crypto: don'T do your own.

Aside from all that: Your mentioned "usecase" doesn'T require what you try to do. You said that the server is already in the classpath - so it gets loaded on startup and you can access the XServer class. The easiest way instead of forking another process is to just run it in another thread. If you know wich class has the main (the manifest of the server.jar will tell you) and you can access it in classpath just do something like this:

Thread serverThread=new Thread(new Runnable()
{
	public void run()
	{
		String[] args=Arrays.asList("required", "parameters");
		XServer.main(args);
	}
});
serverThread.start();

If no paramters required you can just pass an empty String array. As main() should not throw Exceptions (at least no checked ones) no exception should be needed.

Before all those comments are thrown at me: Yes, I am very well aware of possible issues with such approach like classpath issues (same classname in same packagename but different versions) and such it may be more feasible than try to figure out the absolute path and launch a fork / sub process.
Also: Starting another process may require to interact with its streams (provide required input into child process inputstream and read the child process outputstream and errorstream - otherwise the forked process may can "hang" as it waits for the pipelines to get cleared. It's a pain in the but to debug that kind of issue if it's not your own code and you can have a profiler and debugger attached to it to figure out why all just suddenly stopped to work.

If you really want to (I don't think there's any requirement forcing a "you need to") launch your server along with the client do it with a launch script outside of java but with os level stuff.

huangapple
  • 本文由 发表于 2020年3月16日 18:47:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/60704524.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定