2009-06-02 11 views
17

Quiero determinar el nombre de clase donde comenzó mi aplicación, la que tiene el método main(), en tiempo de ejecución, pero estoy en otro hilo y mi stacktrace no vuelve a la clase original.¿Cómo se determina la clase principal en tiempo de ejecución en la aplicación de Java con subprocesos?

He buscado las propiedades del sistema y todo lo que ClassLoader tiene para ofrecer y no se me ocurre nada. ¿Esta información simplemente no está disponible?

Gracias.

+4

Tengo curiosidad si pudiera explicar por qué está buscando esto ..? –

+1

Para configurar niveles de registro en un archivo de configuración común para múltiples aplicaciones. –

+1

Estoy de acuerdo con matt b. En mi opinión, eso nunca debería ser necesario. Parece que su sistema intenta ser más consciente de sí mismo de lo que es bueno para sí mismo. –

Respuesta

2

Me di cuenta. ¿Alguien puede decirme si esta variable de entorno siempre estará presente en otras implementaciones de Java en los sistemas operativos? Esto en Oracle JVM produce una cadena como "org.x.y.ClassName"

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."); 
} 
+1

¿Por qué no simplemente System.getenv ("JAVA_MAIN_CLASS")? –

+3

La mía tenía un número (pid?) Clavado al final de la llave. p.ej. "JAVA_MAIN_CLASS_13833" De ahí el startsWith(). –

+0

Eso no está disponible en IBM 1.5.0 JRE, que estoy usando –

3

Intente utilizar Thread.getAllStackTraces(). Devuelve un Mapa de los rastros de pila de todos los hilos en ejecución, no solo el actual.

+1

Eso realmente no ayuda, ya que el hilo que inicialmente ejecutó la clase principal ya no necesita ejecutarse. –

+5

(No ayuda en todos los casos imaginables)! = (No ayuda para nada). –

+1

Estoy de acuerdo y me di cuenta de que después de publicar el comentario. Aún así, es bueno estar al tanto de los límites de una solución. –

0

¿Qué tal algo como:

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); 
     } 
    } 
} 

Esto le dará algo así como

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) 

El hilo principal es, probablemente, no guarenteed a llamarse "main" aunque - podría ser mejor para comprobar si hay una pila elemento traza que contiene (main

Editar si el principal thr ead ha salido, ¡esto no está bien!

+0

He intentado esto. No está en mi stack. Esta fue mi primera conjetura, también. –

+0

Agregué una edición cuando publicaste este comentario. Si el hilo ya no existe, no estoy seguro de que se pueda hacer mucho –

2

Dada la aclaración, se sugiere emplear la "Parametrización desde arriba" expresión idiomática. Usted tiene la información para comenzar, manténgala.

Puse en una RFE 4827318 (hace seis años!) Para algo como esto para su uso con corredores de prueba.

+0

Resuelto el año pasado, es decir, después de 10 años, como "No es un problema". Gracioso, ¿no? – maaartinus

+0

Mi problema es No siempre controlo el método 'main' ... – rogerdpack

0

Sugiero poner esta información en una propiedad del sistema. Esto suele ser fácil de hacer cuando inicia su aplicación desde un script.

Si no puede hacer eso, le sugiero que establezca la propiedad en el método main() de cada aplicación. La forma más simple aquí sería que cada aplicación derive su "clase principal" de una clase base común y ejecute un paso de inicialización allí. A menudo hago esto para el procesamiento de línea de comandos:

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

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

Ver los comentarios en el enlace dado por Tom Hawtin. Una solución es en estos días es Oracle JVM (solamente):

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

Probado sólo con Oracle Java 7. Más información sobre casos especiales: http://bugs.java.com/view_bug.do?bug_id=4827318

+0

¡Me gusta este! Véase también http://stackoverflow.com/a/14122039/3245 3 – rogerdpack

+1

Si se ejecuta desde un jar, esto le da el nombre del archivo jar, no el nombre de la clase principal dentro del jar. – sschuberth

2

El valor medio JAVA_MAIN_CLASS no siempre está presente en función de la plataforma . Si lo que desea es obtener un nombre de la clase "principal" que se inició el proceso de Java, se puede hacer esto:

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

Incluso si el hilo con el método main() ha terminado y que no esté utilizando el Oracle JVM aún puede intentar obtener la información del sistema operativo. El siguiente código obtiene la línea de comando utilizada para iniciar la JVM bajo Linux pero puede escribir una versión para Windows, etc.Luego puede ver los argumentos en la JVM para encontrar el punto de entrada de la aplicación. Podría estar directamente en la línea de comandos o podría haber buscado Main-Class: classname en el manifiesto del jar especificado. Primero usaría System.getProperty ("sun.java.command") y mis amigos solo recurrirían a este mecanismo si fuese necesario.

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; 
    } 
}` 
0

Aquí es lo que estoy usando, por situaciones en las que no controla el principal:

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"); 
} 
0

Otra forma de obtener clase principal es buscar esa clase de Thread.getAllStackTraces, por lo que podría encontrarlo incluso dentro de jar, funciona en cualquier 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; 
    } 
} 
Cuestiones relacionadas