Запуск 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, являются их собственными. |