На прошлой неделе я написал пост о запуске класса Java в качестве подпроцесса . Этот пост был вызван моей необходимостью запустить класс из теста без предварительной сборки Jar. Единственная разница между тем, что я написал в этом посте, и тем, что произошло на самом деле, была языком. Я использовал Котлин, чтобы написать этот тест. Не Java. Поэтому я решил написать этот пост, который основывается на том, что я ранее написал, и фокусируется на запуске подпроцесса Kotlin вместо подпроцесса Java.
Давайте начнем с эквивалентной реализации exec
из Запуска Java-класса как подпроцесса :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
@Throws (IOException:: class , InterruptedException:: class ) fun exec(clazz: Class<*>, args: List<String> = emptyList(), jvmArgs: List<String> = emptyList()): Int { val javaHome = System.getProperty( "java.home" ) val javaBin = javaHome + File.separator + "bin" + File.separator + "java" val classpath = System.getProperty( "java.class.path" ) val className = clazz.name val command = ArrayList<String>() command.add(javaBin) command.addAll(jvmArgs) command.add( "-cp" ) command.add(classpath) command.add(className) command.addAll(args) val builder = ProcessBuilder(command) val process = builder.inheritIO().start() process.waitFor() return process.exitValue() } |
Краткое объяснение, так как я рассказал обо всем остальном в моем предыдущем посте
Путь к исполняемому файлу Java извлекается и сохраняется в javaBin
. Используя это вместе с переданными в clazz
, args
, jvmArgs
и classpath процесса, команда ProcessBuilder
создает и выполняет ProcessBuilder
. Теперь класс успешно работает как подпроцесс.
Приведенный выше код в значительной степени является копией и вставкой реализации Java. Различия — это порядок параметров функции. Я решил переключить args
и jvmArgs
так, чтобы я мог в полной мере использовать их значения по умолчанию. Это основано на предположении, что вы скорее предоставите args
вместо jvmArgs
. При запуске из Kotlin это не сильно беспокоит, так как вы можете называть параметры при вызове функции. Но если какой-либо Java-код должен вызывать эту функцию, тогда может быть полезен порядок параметров (а также добавление @JvmOverloads
).
Ниже приведены некоторые способы вызова exec
:
1
2
3
4
5
6
7
|
exec(MyProcess:: class .java, listOf( "3" ), listOf( "-Xmx200m" )) // jvmArgs = emptyList() exec(MyProcess:: class .java, listOf( "3" )) // args = emptyList() exec(MyProcess:: class .java, jvmArgs = listOf( "-Xmx200m" )) // both args and jvmArgs = emptyList() exec(MyProcess:: class .java) |
При написании на Kotlin вместо Java заметной разницей является количество способов определения main
функции. Это связано с тем, что Kotlin предоставляет много разных маршрутов для создания статической функции.
Функция, которую я предоставил выше, будет запускать main
функцию в двух следующих сценариях:
Внутри объекта-компаньона и @JvmStatic
1
2
3
4
5
6
7
8
|
class MyProcess { companion object { @JvmStatic fun main(args: Array<String>) { // do stuff } } } |
Внутри объекта и @JvmStatic
1
2
3
4
5
6
|
object MyProcess { @JvmStatic fun main(args: Array<String>) { // do stuff } } |
Еще один способ создать статическую функцию в Kotlin — это определить ее вне класса:
1
2
3
4
5
6
7
|
@file :JvmName( "MyProcess" ) package dev.lankydan fun main(args: Array<String>) { // do stuff } |
Необходимо указать имя, чтобы Java знала, что с ним делать. Выполнение main
функции в этом фрагменте — одна из ситуаций, где это необходимо. К сожалению, функция exec
не будет работать в этой ситуации, так как класс MyProcess
фактически не существует. Предотвращение его компиляции. Как ни странно, вызов MyProcess
из кода Java будет компилироваться без каких-либо изменений.
Чтобы устранить ошибку компиляции, обнаруженную в версии Kotlin, параметры exec
нужно изменить очень незначительно:
1
2
3
4
|
@Throws (IOException:: class , InterruptedException:: class ) fun exec(className: String, args: List<String> = emptyList(), jvmArgs: List<String> = emptyList()): Int { // className passed into command } |
Это изменение позволяет избежать ошибки компиляции, которую представляет исходная функция exec
. Но это связано с недостатком того, что на самом деле не является типобезопасным, и может привести к нескольким ошибкам здесь и там, где передается неверная строка. Как вы можете видеть ниже:
1
|
exec( "dev.lankydan.MyProcess" , listOf( "argument" ), listOf( "-Xmx200m" )) |
Что-то в этом изменении действительно шуршит в моих уловках. Поэтому обеспечение обеих перегрузок является, пожалуй, самым разумным решением.
Подводя итог, хотя большая часть того, что я должен сказать о выполнении классов Kotlin в качестве подпроцессов, уже была рассмотрена в разделе Запуск класса Java в качестве подпроцесса . Есть несколько различий из-за гибкости Kotlin. main
функция в Kotlin может быть определена несколькими способами, и, к сожалению, одна из тех, кто не ладит с exec
функцией, удовлетворяет остальных. К счастью, решение — это небольшое изменение кода, которое может хорошо работать, если есть перегрузки для имен классов String
и Class
.
Если вам понравился этот пост или вы нашли его полезным (или и тем, и другим), пожалуйста, не стесняйтесь, следите за мной в Твиттере на @LankyDanDev и не забудьте поделиться с кем-либо, кто может найти это полезным!
Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Запуск класса Kotlin в качестве подпроцесса Мнения, высказанные участниками Java Code Geeks, являются их собственными. |