Статьи

Запуск класса Kotlin в качестве подпроцесса

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