【问题标题】:Is it possible to embed Java compiler into an application?是否可以将 Java 编译器嵌入到应用程序中?
【发布时间】:2018-07-03 04:43:56
【问题描述】:

使用 Xtext 和 Xtend,我编写了一个 DSL 语法及其相关的代码生成器,它创建了一组 Java 源文件,组成了一个 Java 应用程序。我想为我的同事提供一个程序,让他们选择用我们的领域特定语言编写的文件,运行代码生成器以生成实际 Java 应用程序的 Java 源文件,编译这些文件并启动应用程序。这个过程在我的开发机器上运行良好,因为我已经安装了所有东西,包括 JDK。但是为我的同事提供这个应用程序会迫使他们在使用该应用程序之前安装 JDK。所以我的问题是:是否可以将 Java 编译器嵌入到分发包中?如果没有,您是否看到其他方法?我正在使用 Gradle 和 javapackager + Inno Setup 来生成嵌入 JRE 的分发包。

更新 #1

 override public void afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context)
 {
  // get the class loader
  val URL[] classLoaderUrls = #{new URL("file:jfxrt.jar")}
  val URLClassLoader cl = new URLClassLoader(classLoaderUrls)

  // set Java compiler options
  var CompilerOptions compilerOptions = new CompilerOptions
  compilerOptions.sourceLevel = ClassFileConstants.JDK1_8
  compilerOptions.complianceLevel = ClassFileConstants.JDK1_8
  val Map<String, String> options = new HashMap
  options.put(CompilerOptions.OPTION_ReportMissingSerialVersion, CompilerOptions.IGNORE)
  options.put(CompilerOptions.OPTION_ReportUnusedParameter, CompilerOptions.IGNORE)
  compilerOptions.set(options)

  val InMemoryJavaCompiler compiler = new InMemoryJavaCompiler(cl, compilerOptions)

  // compile all generated Java source files
  val result = compiler.compile(new JavaSource("xxxx/Bit.java", bitGen.javaSource.toString),
   new JavaSource("xxxx/BitNature.java", bitNatureGen.javaSource.toString),
   new JavaSource("xxxx/ImageMaker.java", imgMakerGen.javaSource.toString),
   new JavaSource("xxxx/MyApp.java", myAppGen.javaSource.toString))

  val URLClassLoader runcl = new URLClassLoader(classLoaderUrls, result.classLoader)

  val Class<?> mainClazz = runcl.loadClass("xxxx.MyApp")

  val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))
  val String[] args = #["C:\\temp\\memory_a.yyy"]
  try
  {
   mainMethod.invoke(null, #[args])
  }
  catch (InvocationTargetException e)
  {
   e.getCause().printStackTrace()
  }
  catch (Exception e)
  {
   // generic exception handling
   e.printStackTrace();
  }
 }

更新 #2

单步执行我的代码,我可以看到以下内容:

1 - 该语句执行没有问题(mainClazz 似乎是一个有效的参考)

val Class<?> mainClazz = result.classLoader.loadClass("xxxx.MyApp")

2 - mainMethod 在我执行时似乎也是一个有效的参考

val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))

3 - 当我执行时事情变糟了

mainMethod.invoke(null, #[args])

问题出现在JavaFX Application类,launch方法上如下语句(ClassNotFoundException: xxxx.MyApp)

Class theClass = Class.forName(callingClassName, false,
                               Thread.currentThread().getContextClassLoader());

【问题讨论】:

  • 你可以直接在java文件上调用jdt编译器(也在内存中)你可以看看例如在 xbase 测试 api 中的 InMemoryJavaCompiler
  • 谢谢@Alan,我正在研究 Java 9 jlink 解决方案以及 InMemoryJavaCompiler 解决方案。
  • 感谢@Christian 遵循您的建议,我现在在生成器的 afterGenerate 方法中使用 InMemoryJavaCompiler。编译没问题,但是在执行val Method mainMethod = mainClazz.getMethod("main", typeof(String[])); 时事情变得很糟糕,我在 JavaFX Stage 类上得到了 ClassNotFoundException。我怎样才能解决这个问题?有没有办法设置类路径以使其包含 JavaFX 包?
  • @Christian 通过查看compiler.compile(... 返回的Result,我可以看到编译器默默地产生了很多错误。你能告诉我如何设置 InMemoryJavaCompiler 的类路径吗?

标签: javac xtext xtend


【解决方案1】:
package org.xtext.example.mydsl2.tests

import java.lang.reflect.Method
import java.net.URLClassLoader
import org.eclipse.xtext.util.JavaVersion
import org.eclipse.xtext.xbase.testing.InMemoryJavaCompiler
import org.eclipse.xtext.xbase.testing.JavaSource

class SampleXXX {

    def static void main(String[] args) {
        val urls = #[]
        val classLoader = new URLClassLoader(urls)
        val compiler = new InMemoryJavaCompiler(classLoader, JavaVersion.JAVA8)
        val result = compiler.compile(new JavaSource("demo/Demo.java", '''
        package demo;
        public class Demo {
            public static void main(String[] args) throws Exception {
                Class theClass = Class.forName("demo.Demo", false,
                    Thread.currentThread().getContextClassLoader());
            }
        }
        '''))
        val URLClassLoader runcl = new URLClassLoader(#[], result.classLoader)
        (new Thread() {

            override run() {
            val Class<?> mainClazz = runcl.loadClass("demo.Demo")
            val String[] args2 = #["C:\\temp\\memory_a.yyy"]
            val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))
            mainMethod.invoke(null, #[args2])
            }

        }=>[
            contextClassLoader = runcl
        ]).start
    }

}

【讨论】:

  • 上下文类加载器实际上是由JavaFX Application类的launch方法使用的。老实说,我从来没有对类加载器感到满意,但我理解你解决我问题的优雅方式。非常感谢!
猜你喜欢
  • 2011-08-30
  • 1970-01-01
  • 2012-04-12
  • 1970-01-01
  • 2018-11-15
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 1970-01-01
相关资源
最近更新 更多