如何在Java中启动一个新进程的“主”?

14

这个问题很简单。我该如何启动另一个Java进程中的主方法?现在我是这样做的:

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

但他们要求我不使用外部的.jar文件来完成。serverstart.jar显然有一个main方法,但是是否可以在另一个进程中调用该main方法,而无需调用.jar文件本身?

我正在考虑这样的方法:

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

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


在你的ProcessBuilder中将当前classpath复制为参数,这样做有帮助吗?是否允许?... System.getProperty("java.class.path") - laher
我不知道,那我该怎么做呢? - Walle
然后你会有一个classpath,你只需要将它传递给-cp并加载java - jmj
5个回答

8
假设使用新的线程和类装载器还不够(尽管我会支持这种解决方案),我理解你需要创建一个独立的进程来调用一个在清单文件中没有被声明为“jar主方法”的类中的主方法 -- 因为你不再有一个独立的 serverstart.jar。
在这种情况下,你可以简单地调用java -cp $yourClassPath your.package.ServerStart,就像运行任何Java应用程序一样,当你没有(或不想使用)清单Main-Class时。

2
假设您的 user.dir 是 Maven 项目根目录,则不应将 target/classes 用作包前缀,而应将其添加到文件系统路径中:new String[] {"java", "-cp", System.getProperty("user.dir") + "/target/classes", ServerStart.class.getName()} - Costi Ciudatu
太好了!我现在在正确的课程中,但是现在我有一个问题,他找不到指定的.jar文件。在我的ServerStart中,他导入了从hsqldb启动的服务器,但是他找不到它。 Caused by: java.lang.ClassNotFoundException: org.hsqldb.Server - Walle
然而,依赖于运行Maven项目根目录中的应用程序并不是一个好主意,因此我认为更好的解决方案是简单地重用当前过程的类路径(并将当前目录作为备选):new String[] {"java", "-cp", System.getProperty("java.class.path", "."), ServerStart.class.getName()} - Costi Ciudatu
另一个错误:´java.lang.NoClassDefFoundError: sample/plugin/hello_maven_plugin/ServerStart Caused by: Java.lang.ClassNotFoundException: sample.plugin.hello_maven_plugin.ServerStart´ - Walle
我已经解决了问题,但现在又出现了另一个问题。我使用循环来保持服务器的运行,当我运行命令时,我的 cmd 陷入了那个循环中。如果我像在第一篇帖子中那样使用 -jar 选项运行代码,则 cmd 不会被困在循环中,因此我仍然可以使用它。所以这种方法实际上并没有做到我想要做的事情。 - Walle
显示剩余6条评论

8
从Java创建一个新的“java”进程是不可能的,因为两个进程无法共享一个JVM。 (参见此问题和被接受的答案)。
如果您可以接受创建一个自定义的ClassLoader而不是一个Process,那么您可以使用它来代替。这是最接近新进程的方式。所有静态和final字段将被重新初始化!另外请注意,下面示例中的"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);
        }
    }
}

创建一个新的 "java" 进程从 Java 不可能,因为两个进程不能共享一个 JVM。-- 也许我漏掉了什么,但我没有看到共享一个 JVM 是一个要求。 - Costi Ciudatu
@Costi,不是的,但ServerStart.main(startOptions)表明他可能想从JVM内部调用main方法。 - aioobe
当然可以,但那只是一个方法调用,与任何新进程无关。 - Costi Ciudatu
你不觉得继承URLClassLoader比继承ClassLoader更好吗?这样你就可以轻松处理Jars和类目录了。 - Paul Cager
@aioobe:我现在明白了,ProcessBuildermain()方法调用结合使用确实会造成这种混淆,所以dacwe一开始就澄清是正确的。 - Costi Ciudatu

1
我建议从Java中调用一个shell脚本,并使用它来启动新进程(如果你不能完全接受只有另一个线程的话)。

0

我在这里回答如何创建没有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应用程序,您可以从配置文件中收集JavaProcess实例。我在这里创建了一个虚拟进程,在出现错误时,我会通过回调函数停止它。
    //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();
    }

-2

你可以使用反射(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);
}

5
这不会启动新进程,并且可能不会重新初始化静态和常量变量。 - dacwe
3
无法工作。您需要一个自定义的ClassLoader来重新初始化所有变量。 - dacwe

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