如何在线程 Java 应用程序中在运行时确定主类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/939932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 14:28:26  来源:igfitidea点击:

How to determine main class at runtime in threaded java application?

javaruntimestack-traceclassloadermain

提问by Erik R.

I want to determine the class name where my application started, the one with the main() method, at runtime, but I'm in another thread and my stacktrace doesn't go all the way back to the original class.

我想在运行时确定我的应用程序启动的类名,那个带有 main() 方法的类名,但我在另一个线程中,我的堆栈跟踪并没有一直回到原始类。

I've searched System properties and everything that ClassLoader has to offer and come up with nothing. Is this information just not available?

我搜索了系统属性以及 ClassLoader 必须提供的所有内容,但一无所获。这些信息只是不可用吗?

Thanks.

谢谢。

采纳答案by Erik R.

I figured it out. Can anyone tell me if this environment variable will always be around in other java implementations across operating systems? This on Oracle JVM yields a String like "org.x.y.ClassName"

我想到了。谁能告诉我这个环境变量是否会一直存在于跨操作系统的其他 Java 实现中?这在 Oracle JVM 上产生一个像“org.xyClassName”这样的字符串

public static String getMainClassName() {
  for (final Map.Entry<String, String> entry : System.getenv().entrySet())
    if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) // like JAVA_MAIN_CLASS_13328
      return entry.getValue();
  throw new IllegalStateException("Cannot determine main class.");
}

回答by Bruno Eberhard

See the comments on link given by Tom Hawtin. A solution is these days is (Oracle JVM only):

请参阅 Tom Hawtin 对链接的评论。现在的解决方案是(仅限 Oracle JVM):

public static String getMainClassAndArgs() {
    return System.getProperty("sun.java.command"); // like "org.x.y.Main arg1 arg2"
}

Tested only with Oracle Java 7. More information about special cases: http://bugs.java.com/view_bug.do?bug_id=4827318

仅使用 Oracle Java 7 进行测试。有关特殊情况的更多信息:http: //bugs.java.com/view_bug.do?bug_id=4827318

回答by Dave

The JAVA_MAIN_CLASS environment value isn't always present depending on the platform. If you just want to get a name of the "main" class that started your Java process, you can do this:

JAVA_MAIN_CLASS 环境值并不总是存在,具体取决于平台。如果您只想获取启动 Java 进程的“主”类的名称,您可以这样做:

  public static String getMainClassName()
  {
    StackTraceElement trace[] = Thread.currentThread().getStackTrace();
    if (trace.length > 0) {
      return trace[trace.length - 1].getClassName();
    }
    return "Unknown";
  } 

回答by Bill the Lizard

Try using Thread.getAllStackTraces(). It returns a Map of the stack traces from all running threads, not just the current one.

尝试使用Thread.getAllStackTraces()。它返回来自所有正在运行的线程的堆栈跟踪的 Map,而不仅仅是当前线程。

回答by Tom Hawtin - tackline

Given the clarification, I suggest using the "Parameterisation from Above" idiom. You have the information to start with, keep hold of it.

鉴于澄清,我建议使用“从上面参数化”成语。你有开始的信息,保持它。

I did put in an RFE 4827318(six years ago!) for something like this for use with test runners.

我确实输入了 RFE 4827318(六年前!),用于与测试运行器一起使用的类似内容。

回答by William Deans

Even if the thread with the main() method has terminated and you are not using the Oracle JVM you can still attempt to get the information from the operating system. The code below obtains the command line used to start the JVM under Linux but you could write a version for Windows, etc. You could then look at the arguments to the JVM to find the application entry point. It might be directly on the command line or you might have look for Main-Class: classname in the manifest of the specified jar. I would first use System.getProperty("sun.java.command") and friends only falling back to this mechanism if necessary.

即使使用 main() 方法的线程已经终止并且您没有使用 Oracle JVM,您仍然可以尝试从操作系统获取信息。下面的代码获取用于在 Linux 下启动 JVM 的命令行,但您可以为 Windows 等编写一个版本。然后您可以查看 JVM 的参数以找到应用程序入口点。它可能直接在命令行上,或者您可能在指定 jar 的清单中查找 Main-Class: classname。我会首先使用 System.getProperty("sun.java.command") 和朋友们只有在必要时才回退到这种机制。

public final static long getSelfPid() {
    // Java 9 only
    // return ProcessHandle.current().getPid();
    try {
        return Long.parseLong(new File("/proc/self").getCanonicalFile().getName());
    } catch( Exception e ) {
        return -1;
    }
}

public final static String getJVMCommandLine() {
    try {
        // Java 9 only
        // long pid = ProcessHandle.current().getPid();
        long pid = getSelfPid();
        byte[] encoded = Files.readAllBytes(Paths.get("/proc/"+pid+"/cmdline"));
        // assume ISO_8859_1, but could look in /proc/<pid>/environ for LANG or something I suppose
        String commandLine = new String( encoded, StandardCharsets.ISO_8859_1 ); 
        String modifiedCommandLine = commandLine.replace((char)0, ' ').trim();
        return modifiedCommandLine;
    } catch( Exception e ) {
        return null;
    }
}`

回答by Ajax

Here's what I'm using, for situations where you don't control the main:

这是我正在使用的,对于您无法控制主要的情况:

public static Class<?> getMainClass() {
  // find the class that called us, and use their "target/classes"
  final Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
  for (Entry<Thread, StackTraceElement[]> trace : traces.entrySet()) {
    if ("main".equals(trace.getKey().getName())) {
      // Using a thread named main is best...
      final StackTraceElement[] els = trace.getValue();
      int i = els.length - 1;
      StackTraceElement best = els[--i];
      String cls = best.getClassName();
      while (i > 0 && isSystemClass(cls)) {
        // if the main class is likely an ide,
        // then we should look higher...
        while (i-- > 0) {
          if ("main".equals(els[i].getMethodName())) {
            best = els[i];
            cls = best.getClassName();
            break;
          }
        }
      }
      if (isSystemClass(cls)) {
        i = els.length - 1;
        best = els[i];
        while (isSystemClass(cls) && i --> 0) {
          best = els[i];
          cls = best.getClassName();
        }
      }
      try {
        Class mainClass = Class.forName(best.getClassName());
        return mainClass;
      } catch (ClassNotFoundException e) {
        throw X_Util.rethrow(e);
      }
    }
  }
  return null;
}

private static boolean isSystemClass(String cls) {
  return cls.startsWith("java.") ||
      cls.startsWith("sun.") ||
      cls.startsWith("org.apache.maven.") ||
      cls.contains(".intellij.") ||
      cls.startsWith("org.junit") ||
      cls.startsWith("junit.") ||
      cls.contains(".eclipse") ||
      cls.contains("netbeans");
}

回答by jalopezsuarez

Another way to get main class is look for that class on Thread.getAllStackTraces, so you could find it even inside jars, it works on any SDK (Open, Oracle...):

获取主类的另一种方法是在 Thread.getAllStackTraces 上查找该类,因此您甚至可以在 jars 中找到它,它适用于任何 SDK(Open、Oracle...):

private static Class<?> mainClass = null;

public static Class<?> getMainClass()
{
    if (mainClass == null)
    {
        Map<Thread, StackTraceElement[]> threadSet = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> entry : threadSet.entrySet())
        {
            for (StackTraceElement stack : entry.getValue())
            {
                try
                {
                    String stackClass = stack.getClassName();
                    if (stackClass != null && stackClass.indexOf("$") > 0)
                    {
                        stackClass = stackClass.substring(0, stackClass.lastIndexOf("$"));
                    }
                    Class<?> instance = Class.forName(stackClass);
                    Method method = instance.getDeclaredMethod("main", new Class[]
                    {
                        String[].class
                    });
                    if (Modifier.isStatic(method.getModifiers()))
                    {
                        mainClass = instance;
                        break;
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
        return mainClass;
    }
}

回答by Aaron Digulla

I suggest to put this information into a system property. This is usually simple to do when you start your application from a script.

我建议将此信息放入系统属性中。当您从脚本启动应用程序时,这通常很容易做到。

If you can't do that, I suggest to set the property in the main() method of every application. The most simple way here would be to have every app derive it's "main class" from a common base class and run an init step in there. I often do this for command line processing:

如果你不能这样做,我建议在每个应用程序的 main() 方法中设置属性。这里最简单的方法是让每个应用程序从一个公共基类派生它的“主类”,并在那里运行一个 init 步骤。我经常在命令行处理中这样做:

public class Demo extends Main {
    main(String[] args) {
        Main._main(new Demo (), args);
    }

    // This gets called by Main._main()
    public void run (String[] args) {
    }
}

回答by Harry Lime

How about something like:

怎么样:

Map<Thread,StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
for (Thread t : stackTraceMap.keySet())
{
    if ("main".equals(t.getName()))
    {
        StackTraceElement[] mainStackTrace = stackTraceMap.get(t);
        for (StackTraceElement element : mainStackTrace)
        {
            System.out.println(element);
        }
    }
}

This will give you something like

这会给你类似的东西

java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:231)
java.lang.Thread.join(Thread.java:680)
com.mypackage.Runner.main(Runner.java:10)

The main thread is probably not guarenteed to be called "main"though - might be better to check for a stack trace element that contains (main

"main"尽管可能不保证调用主线程- 最好检查包含以下内容的堆栈跟踪元素(main

Editif the main thread has exited, this is no good!

编辑如果主线程已经退出,这是不行的!