你能否在运行时确定是否从jar包中运行java?

49

我有一个应用程序,有些用户从Eclipse运行,其他用户则使用jar文件运行。

我想要在从jar文件中运行时执行某些操作,但不希望在从Eclipse运行时执行这些操作。

是否有一种方法可以在运行时知道当前应用程序是否从jar文件中运行?

谢谢!

Dikla


你的用户如何从Eclipse中运行你的应用程序?作为插件?作为外部工具?还是从bin文件夹中作为简单类? - Hosam Aly
Dikla,我假设你需要这个快速而简单的设置,检查System.console() == null,通常情况下,当从eclipse启动时,控制台将为null。 - Ustaman Sangat
但我猜测无头启动进程(例如在Windows中的javaw,或在Linux中后台运行进程)也可能会给您一个空控制台 - 请检查;请原谅我的懒惰。 - Ustaman Sangat
一样!在IDE中,我想执行某些“dev”操作,这与运行JAR时完全不同! - marcolopes
8个回答

48

你可以通过使用Foo.class.getResource("Foo.class")并查看返回的URL是否以"jar:"开头来判断一个类是否从JAR文件中加载。

例如,考虑以下程序:

public class Foo {

    public static void main(String[] args) {
        System.out.println(Foo.class.getResource("Foo.class"));
    }
}

从文件系统加载文件并运行它:

file:/C:/Users/Jon/Test/com/whatever/Foo.class

从JAR文件运行它:

jar:file:/C:/Users/Jon/Test/foo.jar!/com/whatever/Foo.class

1
这无法确定他是否在Eclipse内运行,特别是如果其他用户也使用JAR。 - Hosam Aly
6
我将这个问题理解为“从Eclipse创建的构建(即在bin目录中)跑和从一个jar文件中运行有什么区别”。没有提及在Eclipse中使用jar文件。虽然这是一个含糊不清的问题。 - Jon Skeet
我同意你的看法。再次阅读问题后,不清楚他的用户是将应用程序作为Eclipse插件还是正常应用程序(作为外部工具)运行。但我猜在这两种情况下他都会使用JAR文件,因此这可能更多涉及到“启动路径”。 - Hosam Aly
1
请注意,如果您在导出菜单中使用“将所需库打包到生成的JAR中”的选项构建jar文件,则Eclipse将使用其jar-in-jar加载程序,并且资源URL将以“rsrc:”开头(例如,“rsrc:com/whatever/Foo.class”)。因此,要确保您正在从jar文件中运行,您应该检查if (resourcePath.startsWith("jar:") || resourcePath.startsWith("rsrc:")) - Starwarswii
如果您在构建中混淆类名,最好使用 foo.class.getName() + ".class" 而不是硬编码类名。 - MasterHD

19

我有两个更好的解决方案。第一个:

URL url = this.getClass().getClassLoader().getResource("input.txt");
URLConnection urlConnection = url.openConnection();
if (urlConnection instanceof JarURLConnection) {
    // run in jar
} else {
    // run in ide
}

第二:

String protocol = this.getClass().getResource(this.getClass.getName() + ".class").getProtocol();
if(Objects.equals(protocol, "jar")){
    // run in jar
} else if(Objects.equals(protocol, "file")) {
    // run in ide
}

3
仅仅为了检查协议而打开一个 URLConnection 是非常浪费资源的,因此并不比简单地检查协议更好。 - Christopher Schultz
1
this.getClass().getResource("") 会返回 null 吗?应该改为 this.getClass().getResource(this.getClass.getName() + ".class") 吗? - zhenguoli
在Eclipse中,应该是这样的:this.getClass.getSimpleName() - marcolopes

8

如果你查询JAR文件名,在从JAR文件运行时它将正常工作,否则它会返回类似于classes的内容,因此可以使用以下代码:

import java.io.File;

public class JarUtilities
{
    public static String getJarName()
    {
        return new File(JarUtilities.class.getProtectionDomain()
                                .getCodeSource()
                                .getLocation()
                                .getPath())
                       .getName();
    }

    public static boolean runningFromJar()
    {
        return getJarName().contains(".jar");
    }
}

编辑:

如果你需要更高的准确性并且要抵御文件扩展名的更改,检查文件是否包含MANIFEST.MF应该可以起到作用:

public static boolean runningFromJAR()
{
    try
    {
        String jarFilePath = new File(JarUtilities.class.getProtectionDomain()
                .getCodeSource()
                .getLocation()
                .getPath()).
                toString();
        jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");

        try (ZipFile zipFile = new ZipFile(jarFilePath))
        {
            ZipEntry zipEntry = zipFile.getEntry("META-INF/MANIFEST.MF");

            return zipEntry != null;
        }
    } catch (Exception exception)
    {
        return false;
    }
}

1
它说“JarUtilities”在一个Jar中,但不知道调用“JarUtilities”的运行类是否在其中。因此会产生误报匹配。 - deFreitas

8

您可以检查Equinox启动器的系统类路径属性:

if (System.getProperty("java.class.path").contains("org.eclipse.equinox.launcher")) {
    System.out.println("You're running inside Eclipse");
}

还有一些其他可能的属性可以检查,您可以通过Eclipse中的 帮助 -> 关于 -> 配置详细信息 找到。

如果您想知道您是从JAR运行还是从一堆类文件运行,Jon的答案很好。但是,如果在两种情况下都使用JAR,则它将无法告诉您所需的内容。


这是特定于Eclipse的,而不是一般确定您的代码是否在JAR文件中运行的好方法。 - foo
3
@foo 这个问题特别询问如何检测代码是否在Eclipse中运行。其他回答给出了检测JAR文件的好方法。而这个回答提供了一个可能的解决方案来检测Eclipse环境。 - Hosam Aly
无法工作(已在Eclipse 3.8 IDE中测试) - marcolopes

6

来自如何做

package com.rgagnon;

public class HelloClass {
 public static void main(String[] args) {
    new HelloClass().say();
 }

 public void say() {
   String className = this.getClass().getName().replace('.', '/');
   String classJar =  
     this.getClass().getResource("/" + className + ".class").toString();
   if (classJar.startsWith("jar:")) {
     System.out.println("*** running from jar!");
   }
   System.out.println(classJar);
 }
}

Will give:

>jar cvfm Hello.jar manifest.mft com\rgagnon\HelloClass.class
added manifest
adding: com/rgagnon/HelloClass.class (in=1059) (out=601) (deflated 43%)

>java com.rgagnon.HelloClass
file:/C:/DEV/WORK/JAVA/com/rgagnon/HelloClass.class

>java -jar Hello.jar
*** running from jar!
jar:file:/C:/DEV/WORK/JAVA/Hello.jar!/com/rgagnon/HelloClass.class

正如Hosam Aly所指出的那样,这并没有完全回答问题。我把它留在这里作为维基答案供大家参考。

他无法使用这个来判断自己是否在Eclipse中运行。 - Hosam Aly
2
这开始听起来像指纹识别了...我认为此时了解确切的动机可能是值得的。如果需要区分应用程序是否在控制台上运行,可以检查System.console() == null。当应用程序由eclipse启动时,它为null;但这是一个非常间接的推断。 - Ustaman Sangat

2

以下是您可以从普通模式或作为jar运行的代码。

import java.applet.Applet;
import java.net.URL;
import java.security.CodeSource;

public class TestApplet extends Applet {

    public TestApplet(){    
        URL path = TestApplet.class.getResource("TestApplet.class");

        if(path.toString().startsWith("jar:"))
            System.out.println("I'm in a jar");
        else
            System.out.println("I'm not in a jar");
    }

    public static void main(String args[]) throws Exception{
        new TestApplet();
    }
}

将其打包并运行,使用以下命令:
jar cf test.jar TestApplet.class
java -cp test.jar TestApplet

5
不要使用字符串操作,而是使用URL的getProtocol()方法,如果你在一个JAR包中,它会返回“jar”。 - mwoodman

1
另一种方法是将信息放在清单文件中,并在运行时测试该信息是否可用。
如果可用,则代码是以“-jar”开头启动的。否则,它是以另一种方式启动的,例如直接从Eclipse启动。

例如,您可以在清单中提供一个 SplashScreen-Image,然后 SplashScreen.getSplashScreen() 将返回非空。 - Matthew Wise

1

你可以尝试:

boolean inJar = false;

try
{
  CodeSource cs = DataResolver.class.getProtectionDomain().getCodeSource();
  inJar = cs.getLocation().toURI().getPath().endsWith(".jar");
}
catch (URISyntaxException e)
{
e.printStackTrace();
}

如果您正在从一个jar文件中运行,则cs.getLocation().toURI()将为您提供该文件的URI;如果您正在从Eclipse内部运行,则它可能是包含您的类文件的目录路径。

2
在这个例子中,路径总是以“.class”结尾,即使在jar包中也是如此。 - mwoodman

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