【问题标题】:Finding Scala libraries location from within Scala program从 Scala 程序中查找 Scala 库位置
【发布时间】:2015-10-31 12:12:24
【问题描述】:

我试图让一个 Scala 程序产生另一个 Scala 程序。我设法从System.getProperty("java.home") 获得了java 可执行文件,我从System.getProperty("java.class.path")sbt-launcher.jar 位置)获得了一些路径,并通过ClassLoader 我获得了project/target/scala-2.11/classes 目录。

但是,我仍然无法运行它。 JVM 抱怨找不到 Scala 库的类:

Exception in thread "main" java.lang.NoClassDefFoundError: scala/concurrent/ExecutionContext
  at java.lang.Class.getDeclaredMethods0(Native Method)
  at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
  at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
  at java.lang.Class.getMethod0(Class.java:3018)
  at java.lang.Class.getMethod(Class.java:1784)
  at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: scala.concurrent.ExecutionContext
  at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
  at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
  at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more

我正在寻找一种将这些文件添加到类路径的方法,但我希望它是可移植的。我不想在本地计算机上寻找诸如硬编码 scala 位置之类的解决方案,也不想使用现有环境变量和参数之外的其他环境变量和参数。我也不想依赖 SBT 或 Activators 在用户环境中的存在。

由于父 JVM 进程可以使用它们,它们的位置必须存储在某个地方,我将感谢您帮助我找出该位置。

【问题讨论】:

    标签: scala classpath main


    【解决方案1】:

    为了从另一个成功地生成一个 Scala 应用程序,我必须解决我的代码的几个问题:

    1。正确的main 类:

    object ChildApp extends App {
      println("success")
    }
    

    要确保ChildApp 可由Java 运行,它必须是object。 Scala 没有 static 的概念,但对象方法会(和 main 会)被编译成静态方法。

    2。正确的类名:

    虽然ChildApp.getClass.getName 返回ChildApp$,但它指的是一个对象(这样我们就可以传递其他静态方法类)。 Java 期望在命令行中使用 $ - 在其他工作中,我必须在将其传递给进程构建器之前删除拖尾 $

    3。完整的类路径

    我还没有在System.getPropertiy("java.class.path") 中找到所有使用过的 JAR:

    val pcp = System getPropertiy "java.class.path" split File.pathSeparator // sbt-launcher.jar only
    

    我也没有在SystemClassLoader 中找到它们:

    val scp = ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString) // same as above
    

    我确实使用类的资源从我的项目中找到了编译文件:

    // format like jar:file:/(your-project/compiled.jar)!some/package/ChildApp.class
    lazy val jarClassPathPattern  = "jar:(file:)?([^!]+)!.+".r
    // format like file:/(your-project/compiled/some/package/ChildApp).class
    lazy val fileClassPathPattern = "file:(.+).class".r
    
    val jcp = jarClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
      val jarDir = Paths get (matcher group 2) getParent()
      s"${jarDir}/*"
    } toSet
    
    val fcp = fileClassPathPattern.findFirstMatchIn(pathToClass) map { matcher =>
      val suffix   = "/" + clazz.getName
      val fullPath = matcher group 1
      fullPath substring (0, fullPath.length - suffix.length)
    } toList
    

    最后我找到了所有这些依赖项的存储位置:

    // use App class' ClassLoader instead of system one
    val lcp = ChildApp.getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs.map(_.toString)
    

    4。奖金 - JVM 参数和 java 位置

    val jvmArgs   = ManagementFactory.getRuntimeMXBean.getInputArguments.toList
    
    lazy val javaHome = System getProperty "java.home"
    
    lazy val java = Seq(
      Paths.get(javaHome, "bin", "java"),
      Paths.get(javaHome, "bin", "java.exe")
    ) filter (Files exists _) head
    

    那么你就拥有了ProcessBuilder/Process所需的一切:

    val executable = java.toString
    val arguments  = jvmArgs ++ List("-cp", classPath, mainName) ++ mainClassArguments
    

    附言。我检查了几次 - 那些 附加 JAR 没有使用 CLASSPATH 环境变量或 -cp 参数传递(sbt-launcher.jar 的 MANIFEST 文件也没有任何内容)。因此,任何知道它们是如何通过以及为什么我的解决方案实际上有效的人,请解释一下。

    【讨论】:

      猜你喜欢
      • 2021-03-24
      • 2017-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-13
      • 1970-01-01
      相关资源
      最近更新 更多