有没有一种方法可以知道Java程序是从命令行启动还是从jar文件启动?

33
我希望能够在控制台或弹出窗口中显示消息,以便在未指定参数的情况下,我知道应该显示到哪里。
类似于:
if( !file.exists() ) {
    if( fromCommandLine()){
        System.out.println("File doesn't exists");
    }else if ( fromDoubleClickOnJar() ) {
        JOptionPane.showMessage(null, "File doesn't exists");
    }
 }

1
如果您从命令行启动它,应用程序是否可以在没有 GUI 的情况下运行? - Jonathan Holloway
有趣的问题(+1),但非常棘手。抱歉删除答案,我意识到这不是可靠的。我会进行一些测试。 - BalusC
@Jon 在这两种情况下都带有图形用户界面。 - OscarRyz
@BalusC确实。事实上,你可以从命令行运行jar文件使情况变得更糟,但我认为一定有办法知道类是从哪里加载的,只是我不记得具体是怎么做的了。 - OscarRyz
1
类似的问题可以参考 https://dev59.com/2UzSa4cB1Zd3GeqPoaQz - Stephen Denne
7个回答

18
直截了当的回答是你无法确定JVM是如何启动的。
但对于你问题中的示例用例,你并不需要知道JVM是如何启动的。你真正需要知道的是用户是否会看到控制台上的消息。而要做到这一点,可以使用以下方式:
if (!file.exists()) {
    Console console = System.console();
    if (console != null) {
        console.format("File doesn't exists%n");
    } else if (!GraphicsEnvironment.isHeadless()) {
        JOptionPane.showMessage(null, "File doesn't exists");
    } else {
        // Put it in the log
    }
 }
控制台的javadoc,虽然不完全准确,但强烈暗示控制台对象(如果存在)写入控制台并且无法重定向。
感谢@Stephen Denne提供!GraphicsEnvironment.isHeadless()技巧。

2
GraphicsEnvironment.isHeadless() 只检查一个属性(作为一种“请勿尝试”设置很有用)。您还需要捕获 HeadlessException,以防该属性未设置,但没有可用的显示器(或键盘或鼠标)。 - Stephen Denne

4

我不是很清楚问题的意思,但我将解释为您想区分以下两个:

java -jar fred.jar

java package.Main

以下是程序的概要:

第一种方式使用了可执行的JAR文件“fred.jar”,而第二种方式则需要指定Java类的完整路径。如果您有一个打包好的JAR文件,那么第一种方式更加方便;如果您只有Java源代码,则必须使用第二种方式来编译和运行程序。

import sun.jvmstat.monitor.*;
...
HostIdentifier hostId = new HostIdentifier("localhost");
MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(hostId);
Set jvms = monitoredHost.activeVms();
for (Object i: jvms) {
   VmIdentifier id = new VmIdentifier("//" + i + "?mode=r");
   MonitoredVm vm = monitoredHost.getMonitoredVm(id, 0);   
   System.out.println(i);
   System.out.println("\t main class: " + MonitoredVmUtil.mainClass(vm, false));
   System.out.println("\t main args: " + MonitoredVmUtil.mainArgs(vm));
   System.out.println("\t jvmArgs: " + MonitoredVmUtil.jvmArgs(vm));
   monitoredHost.detach(vm);
}

MonitoredVmUtil.mainClass(vm, false)的调用将返回"jar"或者您主类的名称,例如"Main"

您需要使用$JAVA_HOME/lib/tools.jar进行编译和运行。


3
System.console() 技巧似乎能够完成工作。
这里有一个替代方法:在 Class 类中有一个方法 getProtectionDomain(),可以用来了解代码来源和位置。
有趣的是,这个方法从1.2版本就已经可用了。
我知道我以前使用过这个方法,这是由 erickson 给出的 原始答案
这是概念验证的证明:
public class FromJar {
    public static void main( String [] args ) {
        if ( FromJar.class
                 .getProtectionDomain()
                 .getCodeSource()
                 .getLocation()
                 .getFile()
                 .endsWith(".jar") ) {

            javax.swing.JOptionPane.showMessageDialog( null, "Launched from Jar" );

       } else {
            System.out.println("Launched NOT from Jar :P ");
       }
    }
}

这是一个短视频(约1分钟),展示了这段代码的运行过程(并使用 cat 编写)。

Youtube video


1
你想展示什么在 java -cp fromjar.jar FromJar - Stephen Denne
嗯,你的问题是这样的:“如何区分类是作为Java应用程序(作为没有-jar参数的独立类)启动还是作为JAR文件启动?(带有-jar参数)”?你最初的问题并没有暗示这一点。我理解为在两种情况下都是JAR文件。否则检查确实很容易... - BalusC
@BalusC,是的,实际上我需要知道用户是双击了JAR文件还是从控制台启动的。我认为System.console()的答案是正确的。 - OscarRyz

2

您可以尝试以下方法:

if (System.console() != null) {
    // Console attached to the JVM: command prompt output
    System.out.println("...");
} else {
    // No console: use Swing
}

这是不正确的。System.console() 可以返回非空值,但 System.out 仍可能被重定向。(事实上,我可以想到两种情况...) - Stephen C

0

1
不错,我测试过了,但是它从来没有抛出异常。我认为这更有用来测试是否存在任何图形设备(无头或非无头)。 - BalusC
3
如果我打开控制台并运行一个Java程序,图形界面仍然会存在。 - OscarRyz

0

确实无法确定JVM是如何被调用的。 但是...有一种方法可以绕过这个问题。 你假设当用户双击一个JAR文件时,就会运行GUI... 好的。那么让我们扩展这个假设。 检查一下从哪里调用了类,即目录。 检查一下那个目录... 假设这是正常使用,当有一个*.jar文件时,用户必须从jar启动应用程序... 但是一个缺陷是用户也可以点击主类文件。 哈哈哈


0

您可以使用RuntimeMBean.getInputArguments()获取所有输入参数,这可用于检测调试是否已启用。

编辑:但是,-jar参数不是其中之一。 :(


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