Запуск Java-класса (не jar-файла) в качестве подпроцесса — это то, что мне нужно было сделать на этой неделе. Точнее, я хотел порождать новый процесс из теста, а не запускать его внутри теста напрямую (в процессе). Я не думаю, что это что-то необычное или сложное занятие. Но это не то, что мне когда-либо приходилось делать раньше, и я не знал точного кода для написания.
К счастью, быстрый Google и несколько постов переполнения стека позже. Я нашел ответ, который мне был нужен. Хотя ответ на этот вопрос есть, я переписываю его здесь как для себя, так и для вас.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class JavaProcess { private JavaProcess() { } public static int exec(Class clazz, List<String> jvmArgs, List<String> args) 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 = clazz.getName(); List<String> command = new ArrayList<>(); command.add(javaBin); command.addAll(jvmArgs); command.add("-cp"); command.add(classpath); command.add(className); command.addAll(args); ProcessBuilder builder = new ProcessBuilder(command); Process process = builder.inheritIO().start(); process.waitFor(); return process.exitValue(); }} |
Эта статическая функция принимает Class который вы хотите выполнить, вместе с любыми аргументами и аргументами JVM, ожидаемыми main методом класса. Доступ к обоим наборам аргументов позволяет полностью контролировать выполнение подпроцесса. Например, вы можете захотеть выполнить ваш класс с небольшим пространством кучи, чтобы увидеть, как он справляется с нехваткой памяти (для этого я и нуждался).
Обратите внимание, чтобы это работало, класс, который вы хотите выполнить, должен иметь метод main . Это очень важно.
Доступ к пути исполняемого файла Java (хранящегося в javaBin ) позволяет выполнить подпроцесс, используя ту же версию Java, что и основное приложение. Если javaBin был заменен на "java" , то вы рискуете выполнить подпроцесс с версией Java вашего компьютера по умолчанию. Это, вероятно, хорошо в большинстве случаев. Но могут быть ситуации, когда это нежелательно.
Как только все команды добавлены в список command , они передаются в ProcessBuilder . ProcessBuilder берет этот список и использует каждое содержащееся в нем значение для генерации команды. Каждое значение в списке command разделяется пробелами с помощью ProcessBuilder . Есть другие перегрузки его конструктора, одна из которых принимает одну строку, где вы можете вручную определить всю команду самостоятельно. Это избавляет вас от необходимости вручную управлять добавлением аргументов в командную строку.
Подпроцесс запускается с передачей ввода-вывода в процесс, который его выполнил. Это необходимо для того, чтобы увидеть все stdout и stderr он производит. inheritIO является удобным методом и может быть достигнуто путем вызова цепочки следующего кода (также настраивает стандартный stdin подпроцесса):
|
1
2
3
4
|
builder .redirectInput(ProcessBuilder.Redirect.INHERIT) .redirectOutput(ProcessBuilder.Redirect.INHERIT) .redirectError(ProcessBuilder.Redirect.INHERIT); |
Наконец, waitFor указывает исполняющему потоку дождаться завершения порожденного подпроцесса. Неважно, если процесс завершится успешно или ошибки. Пока подпроцесс как-то заканчивается. Основное исполнение может продолжаться. Как завершился процесс, подробно описано в его exitValue . Например, 0 обычно обозначает успешное выполнение, а 1 — недопустимую синтаксическую ошибку. Существует много других кодов выхода, и они могут различаться в зависимости от приложения.
Вызов метода exec будет выглядеть примерно так:
|
1
|
JavaProcess.exec(MyProcess.class, List.of("-Xmx200m"), List.of("argument")) |
Который выполняет следующую команду (или что-то близкое к ней):
|
1
|
/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/bin/java -cp /playing-around-for-blogs MyProcess "argument" |
Я вырезал много путей, включая classpath, чтобы сделать его немного аккуратнее. Ваш, вероятно, будет выглядеть намного дольше, чем этот. Это действительно зависит от вашего приложения на самом деле. Путь в приведенной выше команде — это минимум, необходимый для его запуска (очевидно, настроенный для моей машины).
Метод exec достаточно гибок и полезен в описании того, что происходит. Хотя, если вы хотите сделать его более гибким и применимым в более широком диапазоне ситуаций, я рекомендую вернуть сам ProcessBuilder из метода. Позволяет вам повторно использовать этот фрагмент кода в нескольких местах, обеспечивая при этом гибкость в настройке перенаправлений ввода-вывода, а также возможность решать, следует ли запускать подпроцесс в фоновом режиме или в блоке, и ожидать его завершения. Это будет выглядеть примерно так:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public static ProcessBuilder exec(Class clazz, List<String> jvmArgs, List<String> args) { String javaHome = System.getProperty("java.home"); String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; String classpath = System.getProperty("java.class.path"); String className = clazz.getName(); List<String> command = new ArrayList<>(); command.add(javaBin); command.addAll(jvmArgs); command.add("-cp"); command.add(classpath); command.add(className); command.addAll(args); return new ProcessBuilder(command);} |
Используя любую из этих функций (или обе), вы теперь сможете запускать любой класс, который существует в пути к классам вашего приложения. В моей ситуации это было очень полезно для порождения подпроцессов внутри интеграционного теста без предварительной сборки каких-либо jar-файлов. Это позволило контролировать аргументы JVM, такие как память подпроцессов, которые не могли бы быть сконфигурированы, если бы выполнялись непосредственно внутри существующего процесса.
|
Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Запуск Java-класса в качестве подпроцесса Мнения, высказанные участниками Java Code Geeks, являются их собственными. |