如何在 Java 的新进程中启动“main”?

Posted

技术标签:

【中文标题】如何在 Java 的新进程中启动“main”?【英文标题】:How can I start a 'main' in a new process in Java? 【发布时间】:2011-08-24 18:04:07 【问题描述】:

这个问题很简单。如何在另一个 java 进程中启动 main 方法?现在我这样做:

startOptions = new String[] "java", "-jar", "serverstart.jar";
new ProcessBuilder(startOptions).start();

但他们要求我不要使用外部 .jar 文件。 serverstart.jar明明有main方法,但是不调用.jar文件就可以在另一个进程中调用那个main方法吗?

我在想这样的事情:

new ProcessBuilder(ServerStart.main(startOptions)).start();

但我不知道是否存在类似的东西。

【问题讨论】:

将当前类路径复制为 ProcessBuilder 中的参数是否会有所帮助或被允许? .. System.getProperty("java.class.path") 我不知道,那我该怎么办? 那么您将拥有类路径,您只需将其传递给-cp 并加载java 【参考方案1】:

您可以使用反射(java.lang.reflect 包)来做到这一点。

public static void main(String[] args) throws Exception 
    Class c = Class.forName("ServerStart");
    Class[] argTypes =  args.getClass() ;
    Method m = c.getMethod("main", argTypes);
    Object passedArgv[] =  args ;
    m.invoke(null, passedArgv);

【讨论】:

这不会启动新进程,也可能不会重新初始化静态和最终变量。 不起作用。您需要自定义 ClassLoader 来重新初始化所有变量。【参考方案2】:

不可能从 java 创建一个新的“java”进程,因为两个进程不能共享一个 JVM。 (见question and the accepted answer)。


如果您可以接受创建新的Thread 而不是Process,则可以使用自定义ClassLoader您可以接近一个新的流程。所有静态和最终字段都将重新初始化!

还要注意"ServerStart类(对于下面的例子)必须在当前正在执行的JVM的类路径中):

public static void main(String args[]) throws Exception 
    // start the server
    start("ServerStart", "arg1", "arg2");


private static void start(final String classToStart, final String... args) 

    // start a new thread
    new Thread(new Runnable() 
        public void run() 
            try 
                // create the custom class loader
                ClassLoader cl = new CustomClassLoader();

                // load the class
                Class<?> clazz = cl.loadClass(classToStart);

                // get the main method
                Method main = clazz.getMethod("main", args.getClass());

                // and invoke it
                main.invoke(null, (Object) args);

             catch (Exception e) 
                e.printStackTrace();
            
        
    ).start();

这是自定义类加载器:

private static class CustomClassLoader extends URLClassLoader 
    public CustomClassLoader() 
        super(new URL[0]);
    

    protected java.lang.Class<?> findClass(String name) 
    throws ClassNotFoundException 
        try
            String c = name.replace('.', File.separatorChar) +".class";
            URL u = ClassLoader.getSystemResource(c);
            String classPath = ((String) u.getFile()).substring(1);
            File f = new File(classPath);

            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);

            byte buff[] = new byte[(int) f.length()];
            dis.readFully(buff);
            dis.close();

            return defineClass(name, buff, 0, buff.length, (CodeSource) null);

         catch(Exception e)
            throw new ClassNotFoundException(e.getMessage(), e);
        
    

【讨论】:

Creating a new "java" process from java is not possible since two processes can't share one JVM. -- 也许我遗漏了什么,但我不认为这里需要共享一个 JVM。 @Costi,不,但是 ServerStart.main(startOptions) 表明他有可能想从 JVM 中调用主方法。 可以,但这只是一个方法调用,与任何新进程无关。 继承 URLClassLoader 而不是 ClassLoader 不是更好吗?这样你就可以毫不费力地处理 Jars 和类目录了。 @aioobe:我现在明白了,ProcessBuildermain() 方法调用结合确实会造成这种混乱,所以 dacwe 从一开始就澄清这一点是正确的。【参考方案3】:

假设一个带有新类加载器的新线程是不够的(不过我会投票支持这个解决方案),我知道您需要创建一个不同的进程来调用类中的 main 方法,而无需将其声明为“jar main 方法" 在清单文件中——因为您不再有不同的 serverstart.jar。

在这种情况下,您可以简单地调用java -cp $yourClassPath your.package.ServerStart,就像在没有(或不想使用)清单 Main-Class 时运行任何 java 应用程序一样。

【讨论】:

我像他的一样做到了:startOptions = new String[] "java", "-cp", System.getProperty("user.dir"), "target.classes." + ServerStart.class.getName(); 但它给了我java.lang.NoClassDefFoundError: target/classes/sample/plugin/hello_maven_plugin/ ServerStart (wrong name: sample/plugin/hello_maven_plugin/ServerStart) 所以我想我在正确的目录中,但我猜他找不到正确的课程。 假设你的user.dir是maven项目根目录,target/classes不应该用作包前缀,而是添加到文件系统路径:new String[] "java", "-cp", System.getProperty("user.dir") + "/target/classes", ServerStart.class.getName() 不错!我现在在正确的班级,但是现在我遇到了他找不到指定的 .jar 文件的问题。在我的 ServerStart 中,他启动了一个从 hsqldb 导入的服务器,但他找不到它。 Caused by: java.lang.ClassNotFoundException: org.hsqldb.Server 但是,依赖于从 maven 项目根目录运行应用程序并不是一个好主意,所以我认为更好的解决方案是简单地重用当前进程的类路径(并使用当前目录作为后备):new String[] "java", "-cp", System.getProperty("java.class.path", "."), ServerStart.class.getName() 我在看到你的回复之前写了我之前的评论,但它似乎也解决了你提到的这个最新问题,因为将使用当前进程的完整类路径。【参考方案4】:

我建议从 java 调用一个 shellscript 并使用它来启动新进程(如果你不能只使用另一个线程的话)。

【讨论】:

【参考方案5】:

我将在这里回答如何在没有 spring 的情况下创建多进程应用程序:)。 使用 spring 你可以通过 xml 配置来做到这一点。 多线程是另一回事,这是多进程

如下所示创建一个 JavaProces 类。您可以在您的环境中存储此类的对应方 XML/JSON。然后使用Runtime.getRuntime().exec(processRunnerString); 开始您的流程,

你应该先找到java.exevm args,然后分别设置-classpath然后mainClassargs

最后你会得到类似 java JRE\java.exe -classpath .;*;lib* AClass arg1 - Dprop=val

您可以使用JMX与其他进程通信。

import java.util.Dictionary;
import java.util.List;

public class JavaProcess 

    private String mainClass;
    private Dictionary<String, String> vmParameters;
    private List<String> classPath;
    private List<String> mainArgs;

    public String getMainClass() 
        return mainClass;
    

    public void setMainClass(String mainClass) 
        this.mainClass = mainClass;
    

    public Dictionary<String, String> getVmParameters() 
        return vmParameters;
    

    public void setVmParameters(Dictionary<String, String> vmParameters) 
        this.vmParameters = vmParameters;
    

    public List<String> getClassPath() 
        return classPath;
    

    public void setClassPath(List<String> classPath) 
        this.classPath = classPath;
    

    public List<String> getMainArgs() 
        return mainArgs;
    

    public void setMainArgs(List<String> mainArgs) 
        this.mainArgs = mainArgs;
    


MainRunner 应用程序,您可以从一个 配置文件。我刚刚在这里创建了一个虚拟进程,以防万一 错误我从进程的回调中停止它。

    //process
    JavaProcess jp = new JavaProcess();

    //java class
    jp.setMainClass("com.hmg.vidapter.run.DriverLauncher");

    //main args[]
    List<String> mainArgsList = new ArrayList<String>();
    mainArgsList.add("ABC1 ARG2 ARG3 ARGN");
    jp.setMainArgs(mainArgsList);

    //-classpath
    List<String> classPath = new ArrayList<String>();
    classPath.add("*");
    classPath.add("libs\\*");
    classPath.add("repo\\*");
    jp.setClassPath(classPath);

    //-Dvm args.
    Dictionary<String, String> vmArgs = new Hashtable<String, String>();
    vmArgs.put("-Dcom.sun.management.jmxremote", "");
    vmArgs.put("-Dcom.sun.management.jmxremote.authenticate=false", "");
    vmArgs.put("-Dcom.sun.management.jmxremote.port=1453", "");
    vmArgs.put("-Dcom.sun.management.jmxremote.ssl=false", "");
    jp.setVmParameters(vmArgs);

    String params = JSONUtils.convertToJSON(jp);
    System.out.println(params);

    StringBuilder sb = new StringBuilder("\"" + getJavaExecutablePath()+ "\"");

    sb.append(" ");

    Enumeration<String> vmaEnum = vmArgs.keys();
    while (vmaEnum.hasMoreElements()) 
        String key = vmaEnum.nextElement();
        sb.append(key + " ");
        String val=vmArgs.get(key);
        if(val!=null && !val.isEmpty())
        
            sb.append(val + " ");
        
    

    sb.append(" -classpath ");
    List<String> cps = jp.getClassPath();
    for (String cp : cps) 
        sb.append(cp+";");
    
    sb.append(" ");             
    sb.append(jp.getMainClass());
    sb.append(" ");

    List<String> mainArgs = jp.getMainArgs();
    for (String ma : mainArgs) 
        sb.append(ma+" ");
    

    System.out.println(sb.toString());
    Process p = Runtime.getRuntime().exec(sb.toString());

    //write output
    InputStreamReader isrO = new InputStreamReader(p.getInputStream());
    BufferedReader brO = new BufferedReader(isrO);
    String callBackO = brO.readLine();
    if (callBackO!=null)
    
        System.out.println("Application Output: " + callBackO);         
    

    //write errput
    InputStreamReader isr = new InputStreamReader(p.getErrorStream());
    BufferedReader br = new BufferedReader(isr);
    String callBack = br.readLine();
    if (callBack!=null)
    
        System.err.println("Application Error: "+ callBack);
        //you can do whatever you want if you don't wanna stop it
        p.destroyForcibly();
    

从 java.home 环境中确定 java.exe 的位置。变量

private static String getJavaExecutablePath()
        String javaHome = System.getProperty("java.home");
        File f = new File(javaHome);
        f = new File(f, "bin");
        f = new File(f, "java.exe");
        return f.getAbsolutePath();
    

【讨论】:

以上是关于如何在 Java 的新进程中启动“main”?的主要内容,如果未能解决你的问题,请参考以下文章

java如何启动一个进程

如何更改正在运行的 java 进程的优先级?

如何查看java进程

Android 程序在系统中如何完成启动

多线程

如何:main.py,带有 kivy 轮播代码的新类,kv 文件