Java - запустить main другого класса в другом процессе

Мне нужен чистый способ запуска многих экземпляров программы Java с графическим интерфейсом, и я хочу сделать это программно. «Программа», которую я хочу запустить, представляет собой просто файл .class (скомпилированный файл .java с основным методом), он должен отображать графический интерфейс и работать независимо от других (как собственный процесс). Мне также нужно передать этой программе некоторые аргументы.

Проверьте EDIT5 для получения полного кода рабочего решения.

Вот класс, который должен запускать множество процессов

package startothermain;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 4;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder("java.exe", "-cp", "bin", "Started", "arg0");
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Это класс, который должен запускаться и отображать графический интерфейс.

package startothermain;

import javax.swing.JOptionPane;

public class Started {

    public static void main(String[] args) {
        JOptionPane.showMessageDialog(null, args[0]);
    }
}

Я пробовал предложения ProcessBuilder и Runtime.getRuntime(), которые я нашел в других ответах, но они, похоже, не работают. Я всегда получаю какую-то ошибку «не найдено», например эту

SEVERE: null
java.io.IOException: Cannot run program "java.exe": error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1041)
    at startothermain.Starter.main(Starter.java:35)
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:135)

Я работаю в NetBeans и нажимаю кнопку «Выполнить», поэтому файлы .java должны быть правильно скомпилированы и находиться в одной папке. Я надеюсь, что это не побочная проблема с папками src/build, созданными IDE.

РЕДАКТИРОВАТЬ: я пытался найти java.exe, но я на Linux. Вздох. Я изменил его на «java», но у меня все та же проблема. Должна быть установлена ​​переменная пути. Более того, я беспокоюсь, что если я укажу ему полный путь, он станет непереносимым.

Попытка получить JAVA_HOME в Linux

System.getenv("JAVA_HOME"); // returns null
System.getProperty("java.home"); // returns /usr/lib/jvm/java-7-openjdk-amd64/jre

И в Windows

System.getenv("JAVA_HOME"); // returns C:\Program Files\Java\jdk1.7.0_51
System.getProperty("java.home"); // returns C:\Program Files\Java\jdk1.7.0_51\jre

EDIT2: этот новый код НЕ порождает ошибок, но и не открывает графический интерфейс. Я пробовал это как в Windows, так и в Linux, с теми же результатами.

String javaHome = System.getProperty("java.home");
ProcessBuilder pb = new ProcessBuilder(javaHome + "/bin/java", "-cp", "bin", "Started", "arg0");

EDIT3: я перенаправил потоки ошибок и вывода построителя процессов (а не созданных процессов), и оказалось, что Java ClassLoader не может найти класс. Я предполагаю, что проблема не в JAVA_HOME, а в проблеме пути.

Вот новый код

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 4;
        System.out.println(System.getProperty("java.home")); // prints C:\Program Files\Java\jdk1.7.0_51\jre
        System.out.println(System.getenv("JAVA_HOME")); // prints C:\Program Files\Java\jdk1.7.0_51

        System.out.println("Working Directory = "
                + System.getProperty("user.dir")); // prints C:\Users\Agostino\Documents\NetBeansProjects\StartOtherMain

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder("java", "build.classes.startothermain.Started");
            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

В лог-файле я нахожу эту ошибку

java.lang.NoClassDefFoundError: build/classes/startothermain/Started (wrong name: startothermain/Started)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)
Exception in thread "main" 

EDIT4: теперь код работает, но если я попытаюсь использовать библиотеку .jar в классе Started, ее невозможно будет найти.

См. этот класс Started, который использует библиотеку .jar (JavaTuples), которая добавляется в библиотеки проекта через NetBeans.

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            pb.command("java", "-cp", "./build/classes", fullClassName, "myArg");

            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Фиксированный начальный класс

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            pb.command("java", "-cp", "./build/classes", fullClassName, "myArg");

            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Ошибка в журнале ProcessBuilder

Exception in thread "main" java.lang.NoClassDefFoundError: org/javatuples/Pair
    at startothermain.Started.main(Started.java:28)
Caused by: java.lang.ClassNotFoundException: org.javatuples.Pair
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 1 more

EDIT5: Рабочий код

Стартовый класс

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            String pathToClassFiles = new File("./build/classes").getPath();
            String pathSeparator = File.pathSeparator; // ":" on Linux, ";" on Windows
            String pathToLib = new File("./lib/javatuples-1.2.jar").getPath();
            pb.command("java", "-cp", pathToLib + pathSeparator + pathToClassFiles, fullClassName, "myArg");

            File log = new File("log" + i + ".txt"); //debug log for started process
            try {
                pb.redirectOutput(log);
                pb.redirectError(log);
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Начал занятие

package startothermain;

import javax.swing.JOptionPane;
import org.javatuples.Pair;

public class Started {

    public static void main(String[] args) {
        Pair<String, Integer> pair = Pair.with("One", 1);
        JOptionPane.showMessageDialog(null, args[0] + " " + pair);
    }
}

person Agostino    schedule 17.01.2014    source источник
comment
Ваша трассировка стека, похоже, указывает на то, что она не может найти java.exe, попробуйте указать абсолютный путь.   -  person turbo    schedule 17.01.2014
comment
Или добавьте java.exe в системный путь к файлу. Любые программы, которые вы часто используете, приятно иметь там.   -  person William Morrison    schedule 17.01.2014
comment
Поскольку вы используете какую-то *nix или Mac OS, попробуйте использовать что-то вроде команды "/bin/bash", "-c", "/usr/bin/java -cp ...", если /usr/bin/java", "-cp", ... у вас не работает. Вызов праймера выполнит команду java в вашем терминале.   -  person Roman Vottner    schedule 17.01.2014
comment
@ user1600770, вы можете найти JAVA_HOME переменную среды, а затем вызвать исполняемый файл Java в каталоге /bin. В зависимости от ОС, в которой вы работаете   -  person Roman Vottner    schedule 17.01.2014
comment
@RomanVottner Я обновил вопрос своими попытками относительно этого   -  person Agostino    schedule 17.01.2014
comment
В Linux также нет javaw — просто используйте java.   -  person Ernest Friedman-Hill    schedule 18.01.2014
comment
Поскольку Linux часто отделяет исполняемый файл от каталога установки, он, очевидно, находится в /usr/bin/java, как вы указали в комментарии ранее. Поэтому либо просто используйте "/usr/bin/java", "-cp",..., либо, как указано в комментарии, перед вызовом команды, отправив команду в вашу оболочку "/bin/bash", "-c", "/usr/bin/java -cp ...". И вы должны установить (экспортировать) свой JAVA_HOME!   -  person Roman Vottner    schedule 18.01.2014
comment
@ErnestFriedman-Hill хороший улов, но все еще не работает. Я дошел до того, что код работает без ошибок, но и без открытия графического интерфейса (что и должно быть).   -  person Agostino    schedule 18.01.2014
comment
@RomanVottner JAVA_HOME должен быть установлен системными администраторами (я на рабочей станции), если это проблема, я могу попросить их об этом. Тем не менее, "/usr/bin/java", "-cp",... работает только наполовину (выполняется без ошибок, но не открывает ни один из графических интерфейсов). РЕДАКТИРОВАТЬ: java -version работает в Bash, вы уверены, что он не установлен?   -  person Agostino    schedule 18.01.2014
comment
В Linux (как и в Windows) вы также можете экспортировать (пользовательская) переменная среды без привилегий root/Admin.   -  person Roman Vottner    schedule 18.01.2014
comment
Обрабатываете ли вы свои входящие? и потоки вывода правильно?   -  person Roman Vottner    schedule 18.01.2014
comment
Я только что попробовал на своем компьютере с Windows, где установлен JAVA_HOME. Новый код в EDIT2 выполняется без ошибок, но и ничего не открывается (та же проблема, что и в Linux). Код, который я использую, в значительной степени тот, который я опубликовал. Класс Started не использует поток, он должен просто открывать всплывающее окно.   -  person Agostino    schedule 18.01.2014
comment
@RomanVottner Обнаружена новая проблема, проверьте EDIT3   -  person Agostino    schedule 19.01.2014
comment
Является ли build.classes.startothermain.Started полным именем класса? (так что пакет build.classes.startothermain?) Если пакет только startothermain и класс Started, это может быть ошибкой. Дополнительная информация доступна здесь. Я думаю, что команда должна быть скорее "java", "-cp", "./build/classes", "startothermain.Started"   -  person Roman Vottner    schedule 19.01.2014
comment
@RomanVottner нет, полное имя просто startothermain.Started. Я изменил рабочий каталог перед запуском команды java, чтобы она заработала. Ваш вариант тоже работает. Если вы хотите опубликовать ответ, я приму его вместо своего. Спасибо.   -  person Agostino    schedule 19.01.2014


Ответы (2)


По вашему запросу я резюмирую свои комментарии в ответ.

Ваша первая проблема заключалась в том, что вы пытались вызвать java.exe на компьютере с Linux или Mac, что не соответствует соглашению Microsoft о включении типа файла в имя. Поэтому Linux и Mac обычно используют вместо этого java.

Путь к исполняемому файлу также может варьироваться от компьютера к компьютеру. Поэтому, вероятно, наиболее общий способ - найти исполняемый файл Java с помощью переменной среды JAVA_HOME, которую можно получить в Java через System.getenv("JAVA_HOME"); или через System.getProperty("java.home");. Однако обратите внимание, что они могут явно не указывать на нужный каталог. В частности, некоторые дистрибутивы Linux помещают все двоичные файлы в /bin или /usr/bin, поэтому ${JAVA_HOME}/bin/java может быть недоступно. В этом случае вы должны создать (жесткую) ссылку на исполняемый файл.

Если путь не установлен, вы можете установить его вручную в сеансе вашей консоли, где вы выполняете свое приложение (в Windows set JAVA_HOME=C:\Program Files\Java (или setx в более новых версиях Windows) в Linux export JAVA_HOME=/opt/java). Это также можно сделать с помощью привилегии

Кроме того, настоятельно рекомендуется заботиться входных и выходных потоков при работе с процессами. Связанная статья подробно объясняет, почему вы должны позаботиться об этом, а не только для того, чтобы перехватывать исключения, создаваемые вызванным процессом.

При вызове процесса (особенно с исполняемым файлом Java) у вас есть несколько вариантов: вы можете либо вызвать процесс Java напрямую (при условии, что Java находится в вашем PATH) с помощью new ProcessBuilder("java", "-cp", "path/to/your/binaries", "package.ClassName");, либо вы можете вызвать исполняемый файл Java через оболочку - в Linux f.e. вы также можете использовать new ProcessBuilder("/bin/bash", "-c", "java -cp path/to/your/binaries package.ClassName"); (хотя праймер явно предпочтительнее).

При динамической загрузке классов/библиотек вы должны использовать полное имя класса. Таким образом, если вы вызываете процесс Java в корневом каталоге вашего проекта, а ваш инструмент построения генерирует файлы классов внутри ./build/classes, а ваш класс Test находится в пакете testpackage, вы получите следующий набор: ./build/classes/testpackage/Test.class. Чтобы запустить новый процесс Java, который вызывает основной метод, включенный в ваш Test.class, вы должны использовать следующую команду: java -cp ./build/classes testpackage.Test иначе Java не сможет найти определение класса Test и выполнить основной метод.

Отсутствующие зависимости должны быть добавлены в сегмент пути к классам (-cp ...) вызывающей команды Java. Ф.э: java -cp lib/jar1.jar;lib/jar2.jar;build/classes/* package.ClassName. Несколько архивов или каталогов разделяются ;, а звездочка * также может включать все содержимое каталога.

Осталось одно замечание: если вы когда-нибудь попытаетесь отправить свое «приложение», вам нужно будет адаптировать этот код к более общей версии (файл свойств f.e), так как это, вероятно, не удастся с высокой вероятностью, поскольку путь может быть совершенно другим.

Если я что-то забыл, пожалуйста, дайте мне знать.

person Roman Vottner    schedule 19.01.2014
comment
+1 спасибо. Это работает, но я только что обнаружил еще одну (побочную) проблему. Проверьте РЕДАКТИРОВАТЬ4. Когда я запускаю процесс извне, он не может найти простую библиотеку. Должен ли я начать еще один вопрос? - person Agostino; 19.01.2014
comment
вам нужно предоставить банку, содержащую необходимые классы, в сегменте -cp ... вызываемой вами Java-команды. Пример: java -cp lib/jar1.jar;lib/jar2.jar;build/classes package.ClassName или просто java -cp lib/*;build/classes package.ClassName - person Roman Vottner; 19.01.2014

Вы пытались запустить java.exe из командной строки, если он там тоже не работает, вам нужно указать путь java к установке JAVA, это можно сделать, установив переменную JAVA_PATH в системных переменных, и это должно указывать на вашу папку Jdk bin .

Если это не работает, я думаю, вам нужно указать полный путь к JAVA.exe, так как программа пытается найти этот файл, но не может найти файл и выдает ошибку.

Напротив, вы можете попробовать создать потоки, которые были бы лучшим решением, потому что потоки используют меньше ресурсов и более эффективны.

person vidit bhatia    schedule 17.01.2014
comment
Я предполагаю, что JAVA_PATH установлен, когда я запускаю Java в терминале, я получаю /usr/bin/java. Создание потоков не вариант, так как я хочу запустить полностью независимые программы, а затем закрыть Стартер. Я открыт для предложений, которые не требуют вызова java.exe, если они существуют. Кстати, я на Linux, почему я вызываю exe?! - person Agostino; 17.01.2014
comment
Хорошо, я понял вашу проблему, поэтому ваша программа пытается найти java.exe, который она не может найти. как файл, поэтому вам нужно указать абсолютный путь к java. Более того, если вы хотите закрыть начальное приложение, используйте JAVAW, потому что он не открывает черное окно терминала при попытке запустить программу. - person vidit bhatia; 17.01.2014
comment
@user1600770 user1600770 Какую систему вы используете, java находится в /usr/bin/java, поэтому либо * nix, либо Mac, и вы пытаетесь вызвать java.exe, который является исполняемым файлом Windows - person Roman Vottner; 17.01.2014
comment
Я пробовал javaw вместо java.exe, но все равно получаю сообщение об ошибке не найдено. Есть ли способ сделать это портативным способом? - person Agostino; 17.01.2014
comment
да @Roman Vottner прав, вам нужно указать полный путь к исполняемому файлу java для вашей платформы - person vidit bhatia; 17.01.2014