簡體   English   中英

從IDE運行時,RMI服務器找到scala.Option,但從sbt運行時,無法找到scala.Option

[英]RMI server finds scala.Option when run from IDE but cannot do it when run from sbt

我試圖從另一個生成一個JVM進程,並使它們通過RMI進行通信。 我設法使其可以在IDE中運行,但是由於某些原因,當我嘗試從sbt運行代碼時,它失敗並顯示:

java.rmi.ServerError: Error occurred in server thread; nested exception is: 
  java.lang.NoClassDefFoundError: scala/Option
  at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:417) ~[na:1.8.0_60]
  at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:268) ~[na:1.8.0_60]

我的問題是弄清楚從IDE和SBT運行它之間有什么變化。

首先,我嘗試使用隨機端口號創建注冊表,以避免由於使用的端口而導致失敗:

@tailrec
def getRegister(attemptsLeft: Integer = 10): (Registry, Integer) = {
  val possiblePorts = (1024 to 65536)
  val randomPort    = possiblePorts(scala.util.Random.nextInt(possiblePorts.size))

  Try (LocateRegistry createRegistry randomPort) match {
    case Success(registry) => (registry, randomPort)
    case Failure(ex)       => if (attemptsLeft <= 0) throw ex
                              else getRegister(attemptsLeft - 1)
  }
}

我使用LocateRegistry.createRegistry是因為它應該解決啟動和結束RMI進程以及將當前類路徑傳遞給它的問題。

當我啟動子進程時,我復制了父進程的類路徑-主類包含在同一項目中,因此我可以簡單地復制用於運行父進程的JVM參數,以確保它可以訪問相同的庫。

然后子進程使用以下代碼:

Try {
  val server   = ... // class which will do the job
  val stub     = UnicastRemoteObject.exportObject(server, 0).asInstanceOf[Server]
  val registry = LocateRegistry getRegistry remotePort

  registry.bind(serverName, stub) // throws in SBT, succeeds in IDE
} match {
  case Success(_)  => logger debug "Remote ready"
  case Failure(ex) => logger error("Remote failed", ex)
                      System exit -1
}

我錯過了什么? 使用LocateRegistry.createRegistry應該復制父進程的類路徑(該父進程已經在幾個地方使用Option ,它必須有權訪問該類),子進程也可以訪問該類(我檢查過確定)。 但是由於某些原因,當我從sbt下運行代碼時, LocateRegistry.createRegistry無法將scala.Option位置傳遞給類路徑。

我設法使其能夠設置java.rmi.server.codebase系統屬性。

我不確定到底發生了什么,如果有人真正解釋,我會很高興。 我很瘋狂地猜測,當我運行LocateRegistry getRegistry remotePort它利用了"java.class.path" ,這有點不可靠。

當我從IDE啟動應用程序時,它將所有dep直接傳遞給JVM-使用的所有JAR都出現在java.class.path 另一方面,當我從SBT啟動它時,我得到的只是/usr/share/sbt-launcher-packaging/bin/sbt-launch.jar

我沒有注意到此問題,因為在為子JVM填充類路徑參數時,我不依賴此屬性。 相反,我使用了類似的東西:

lazy val javaHome = System getProperty "java.home"

lazy val classPath = System getProperty "java.class.path"

private lazy val jarClassPathPattern  = "jar:(file:)?([^!]+)!.+".r
private lazy val fileClassPathPattern = "file:(.+).class".r

def classPathFor[T](clazz: Class[T]): List[String] = {
  val pathToClass = getPathToClassFor(clazz)

  val propClassPath   = classPath split File.pathSeparator toSet

  val loaderClassPath = clazz.getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.getFile).toSet

  val jarClassPath    = jarClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
    val jarDir = Paths get (matcher group 2) getParent()
    s"${jarDir}/*"
  } toSet

  val fileClassPath   = fileClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
    val suffix   = "/" + clazz.getName
    val fullPath = matcher group 1
    fullPath substring (0, fullPath.length - suffix.length)
  } toSet

  (propClassPath ++ loaderClassPath ++ jarClassPath ++ fileClassPath ++ Set(".")).toList
}

def getPathToClassFor[T](clazz: Class[T]) = {
  val url = clazz getResource s"${clazz.getSimpleName}.class"
  Try (URLDecoder decode (url.toString, "UTF-8")) match {
    case Success(classFilePath) => classFilePath
    case Failure(_)             => throw new IllegalStateException("")
  }
}

java.rmi.server.codebase重用了這些額外的JAR之后,一切開始可靠地工作:

def configureRMIFor[T](clazz: Class[T]): Unit = {
  val classPath = classPathFor(clazz)
  val codebase  = if (classPath isEmpty) ""
                  else classPath map (new File(_).getAbsoluteFile.toURI.toURL.toString) reduce (_ + " " + _)

  logger trace s"Set java.rmi.server.codebase to: $codebase"
  System setProperty ("java.rmi.server.codebase", codebase)
}

不過,如果有更多知識淵博的人來解釋它究竟有何不同,我還是很好。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM