2009-03-11 17 views
31

¿Se puede cargar una aplicación Java en un proceso separado utilizando su nombre, en lugar de su ubicación, de manera independiente de la plataforma?Ejecutar una aplicación Java en un proceso separado

Sé que puede ejecutar un programa a través de ...

Process process = Runtime.getRuntime().exec(COMMAND); 

... el tema principal de este método es que este tipo de llamadas son a continuación plataforma específica.


Idealmente, me envuelvo un método en algo tan simple como ...

EXECUTE.application(CLASS_TO_BE_EXECUTED); 

... y pase el nombre completo de una clase de aplicación como CLASS_TO_BE_EXECUTED.

+1

Así que si entiendo bien, tiene varias clases con métodos main() y desea iniciarlas en procesos separados? –

+0

¿Qué tal si ejecutas ("java.exe", CLASS_TO_BE_EXECUTED.class.getName())? –

+0

cómo tomar la entrada de un usuario para la clase java que se ejecuta como un proceso iniciado por un programa java, usando algo como br.readLine() – paragjain

Respuesta

34

Dos consejos:

System.getProperty("java.home") + "/bin/java" le ofrece una ruta de acceso al ejecutable java.

((URLClassLoader() Thread.currentThread().getContextClassLoader()).getURL() lo ayuda a reconstruir el classpath de la aplicación actual.

Luego, su EXECUTE.application es sólo (pseudocódigo):

Process.exec(javaExecutable, "-classpath", urls.join(":"), CLASS_TO_BE_EXECUTED)

+8

tercer consejo: no use urls.join (":") si quiere ser independiente de la plataforma. – jdigital

+2

Archivo.some_constant en lugar de: (puede buscar alguna_constant :-) – TofuBeer

+2

http://java.sun.com/j2se/1.4.2/docs/api/java/io/File.html#pathSeparatorCha – Jean

3

¿Realmente tiene que iniciarlos de forma nativa? ¿Podrías llamar directamente a sus métodos "principales"? Lo único especial de main es que el iniciador de VM lo llama, nada le impide llamar a main usted mismo.

2
public abstract class EXECUTE { 

    private EXECUTE() { /* Procedural Abstract */ } 

    public static Process application(final String CLASS_TO_BE_EXECUTED) { 

     final String EXEC_ARGUMENT 
     = new StringBuilder(). 
       append(java.lang.System.getProperty("java.home")). 
       append(java.io.File.separator). 
       append("bin"). 
       append(java.io.File.separator). 
       append("java"). 
       append(" "). 
       append(new java.io.File(".").getAbsolutePath()). 
       append(java.io.File.separator). 
       append(CLASS_TO_BE_EXECUTED). 
       toString(); 

     try {  

      return Runtime.getRuntime().exec(EXEC_ARGUMENT); 

     } catch (final Exception EXCEPTION) {  

      System.err.println(EXCEPTION.getStackTrace()); 
     } 

     return null; 
    } 
} 
1

Después de lo TofuBeer tenía que decir: ¿Estás seguro que realmente necesita para bifurcar fuera otra JVM? La JVM tiene un buen soporte para la concurrencia en estos días, por lo que puede obtener una gran cantidad de funcionalidades relativamente baratas simplemente girando uno o dos subprocesos nuevos (que pueden o no requerir llamadas a Foo # main (String [])). Consulte java.util.concurrent para obtener más información.

Si decide hacer un fork, se prepara para un poco de complejidad relacionada con la búsqueda de los recursos necesarios. Es decir, si su aplicación cambia con frecuencia y depende de un grupo de archivos jar, tendrá que hacer un seguimiento de todos ellos para que puedan pasarse a classpath arg. Además, tal enfoque requiere inferir tanto la ubicación de la JVM (actualmente en ejecución) (que puede no ser precisa) como la ubicación de la ruta de clases actual (que es aún menos probable que sea precisa, dependiendo de la forma en que se genere el desove El subproceso se ha invocado: jar, jnlp, explosion .classes dir, algún contenedor, etc.).

Por otro lado, la vinculación a los métodos estáticos #main tiene sus dificultades también. Los modificadores estáticos tienen una desagradable tendencia a filtrarse a otro código y generalmente son mal vistos por personas con mentalidad de diseño.

+0

RMI se involucrará posteriormente, los procesos actúan como Daemons para el proceso principal "Kernel", y existe la posibilidad de que no estén en la misma computadora sino en otra parte de la red. Aunque creo que el fraseo original es útil para más lectores. –

54

Esta es una síntesis de algunas de las otras respuestas que se han proporcionado. Las propiedades del sistema Java proporcionan suficiente información para llegar a la ruta al comando java y al classpath en lo que, creo, es una plataforma independiente.

public final class JavaProcess { 

    private JavaProcess() {}   

    public static int exec(Class klass) throws IOException, 
               InterruptedException { 
     String javaHome = System.getProperty("java.home"); 
     String javaBin = javaHome + 
       File.separator + "bin" + 
       File.separator + "java"; 
     String classpath = System.getProperty("java.class.path"); 
     String className = klass.getCanonicalName(); 

     ProcessBuilder builder = new ProcessBuilder(
       javaBin, "-cp", classpath, className); 

     Process process = builder.start(); 
     process.waitFor(); 
     return process.exitValue(); 
    } 

} 

Se podría ejecutar este método así:

int status = JavaProcess.exec(MyClass.class); 

pensé que tenía sentido para pasar en la clase real en lugar de la representación de cadena del nombre ya que la clase tiene que estar en la ruta de clase de todos modos para que esto funcione.

+3

Es posible que necesite usar 'redirectOutput (...)' y 'redirectError (...)' en el generador de procesos (antes de 'start() ') si desea iniciar algún tipo de servidor; de lo contrario, podría colgar y es posible que no puedas contactarlo. No estoy seguro de por qué, aunque ... – nyg

+0

Cómo ejecutar el nuevo proceso en modo separado, para que el proceso externo pueda continuar su trabajo – daydreamer

+0

¡Gracias! Separar "-cp" del classpath real ayudó. Al juntarlos como una cadena única, se produce un error "Opción no reconocida: -cp " – asgs

4

Esto puede ser una exageración para usted, pero Project Akuma hace lo que quiere y más. Lo encontré a través de this entry en Kohsuke's (uno de los programadores de inicio de rock de Sun) blog fabulosamente útil.

+1

Parece que se movió a Github. Tiene sentido, dado lo agrio que llegó Oracle con toda la cosa de Jenkins/Hudson. – StaxMan

1

Un problema que ocurre al ejecutar esto desde una GUI java es que se ejecuta en segundo plano. Así que no puede ver el símbolo del sistema en absoluto.

Para evitar esto, debe ejecutar java.exe a través de "cmd.exe" Y "iniciar". No sé por qué, pero si pones "cmd/c start" delante, muestra el símbolo del sistema mientras se ejecuta.

Sin embargo, el problema con "start" es que si hay un espacio en la ruta de la aplicación (que el camino hacia el exe java por lo general tienen como lo es en C: \ Archivos de programa \ Java \ jre6 \ bin \ java.exe o similar), y luego el inicio simplemente falla con "can not find c: \ Program"

Debes poner comillas alrededor de C: \ Program Files \ Java \ jre6 \ bin \ java.exe Ahora el inicio se queja de los parámetros que pasa a java.exe: "El sistema no puede encontrar el archivo -cp".

Escapar el espacio en "Archivos de programa" con una barra diagonal inversa tampoco funciona. Entonces la idea es no usar espacio. Genere un archivo temporal con la extensión bat y luego ponga su comando con espacios allí y ejecute el bat. Sin embargo, al ejecutar un inicio de compilación, no finaliza cuando finaliza, , por lo que debe colocar "exit" al final del archivo de proceso por lotes.

Esto todavía parece asqueroso.

Por lo tanto, buscando alternativas, he encontrado que el uso de comillas de espacio de cotización en el espacio de "Archivos de programa" en realidad funciona con inicio.

En la clase anterior EJECUTAR cambio del constructor de cadena anexa a:

append("cmd /C start \"Some title\" "). 
append(java.lang.System.getProperty("java.home").replaceAll(" ", "\" \"")). 
append(java.io.File.separator). 
append("bin"). 
append(java.io.File.separator). 
append("java"). 
append(" "). 
append(new java.io.File(".").getAbsolutePath()). 
append(java.io.File.separator). 
append(CLASS_TO_BE_EXECUTED). 
4

Ampliando la respuesta de @ stepancheg el código real se vería así (en la forma de una prueba).

import org.junit.Test; 

import java.io.File; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.Arrays; 
import java.util.stream.Collectors; 

public class SpinningUpAJvmTest { 
    @Test 
    public void shouldRunAJvm() throws Exception { 
     String classpath = Arrays.stream(((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs()) 
       .map(URL::getFile) 
       .collect(Collectors.joining(File.pathSeparator)); 
     Process process = new ProcessBuilder(
       System.getProperty("java.home") + "/bin/java", 
       "-classpath", 
       classpath, 
       MyMainClass.class.getCanonicalName() 
       // main class arguments go here 
     ) 
       .inheritIO() 
       .start(); 
     int exitCode = process.waitFor(); 
     System.out.println("process stopped with exitCode " + exitCode); 
    } 
} 
Cuestiones relacionadas