【问题标题】:Compile Java class in runtime with dependencies to nested jar在运行时编译具有对嵌套 jar 的依赖项的 Java 类
【发布时间】:2019-05-24 23:26:24
【问题描述】:

在 Spring Boot 应用程序中,我在运行时执行以下操作:

  1. 生成 Java 类
  2. 编译
  3. 使用反射访问已编译类的一些静态字段。

我的代码基于this post,但在运行时编译生成的类时遇到问题。在 IDE 中运行时编译工作正常,但从 Spring Boot 运行时 jar 编译失败,提示符号丢失或某些包不存在。我正在编译的类依赖于位于\BOOT-INF\lib\ 下的 jar 中的其他类,并且编译器似乎无法使用现有的类加载器加载这些类。

我已经关注了 this post,它应该可以解决这个特定问题,但我得到了 UnsupportedOperationException 来自方法

default Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
    throw new UnsupportedOperationException();
}

接口JavaFileManager

我遇到了另一个可能的解决方案here,但我不清楚完整的实现。

在运行时编译类时,这似乎是一个众所周知的问题,有什么明确的解决方案吗?

我目前使用的是 Java 10.0.2。

【问题讨论】:

    标签: spring-boot java-compiler-api runtime-compilation javacompiler


    【解决方案1】:

    虽然您没有明确提到它,但我认为您正在运行带有 modules(JDK 9+)的 Java 版本,但您一直遵循的指南适用于从 Java 6 开始的早期版本。这就是为什么您收到有关不受支持的 listLocationsForModules 的错误,因为 JDK 开发人员使用抛出 UnsupportedOperationException 的默认方法对 FileManager 进行了改造。

    如果你真的不想使用大于 8 的 Java 版本,我会坚持使用 JDK8,它会容易得多!

    我会继续假设您确实想使用 Java 9 及更高版本(在 Java 11 中测试了我的代码):

    对于处理模块,您的文件管理器委托给标准文件管理器就足够了:

    @Override
    public Location getLocationForModule(Location location, String moduleName) throws IOException {
        return standardFileManager.getLocationForModule(location, moduleName);
    }
    
    @Override
    public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
        return standardFileManager.getLocationForModule(location, fo);
    }
    
    @Override
    public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
        return standardFileManager.listLocationsForModules(location);
    }
    
    @Override
    public String inferModuleName(Location location) throws IOException {
        return standardFileManager.inferModuleName(location);
    }
    

    我还发现有必要修改 Atamur 的代码以显式检查基本 java 模块(以便我们可以在 Java 9+ 中解析 java.lang!)并像对平台类一样委托给标准文件管理器以前版本中的路径:

    @Override
    public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
        boolean baseModule = location.getName().equals("SYSTEM_MODULES[java.base]");
        if (baseModule || location == StandardLocation.PLATFORM_CLASS_PATH) { // **MODIFICATION CHECK FOR BASE MODULE**
            return standardFileManager.list(location, packageName, kinds, recurse);
        } else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
            if (packageName.startsWith("java") || packageName.startsWith("com.sun")) {
                return standardFileManager.list(location, packageName, kinds, recurse);
            } else { // app specific classes are here
                return finder.find(packageName);
            }
        }
        return Collections.emptyList();
    
    }
    

    更新

    其他几点:

    提取嵌入式 Spring Boot 类:

    通过查找 '!' 的最后一个索引来获取 jarUri在每个 packageFolderURL 中,就像在 Taeyun Kim's comment 中一样,而不是在原始示例中的第一个。

     private List<JavaFileObject> processJar(URL packageFolderURL) {
      List<JavaFileObject> result = new ArrayList<JavaFileObject>();
      try {
        // Replace:
        // String jarUri = packageFolderURL.toExternalForm().split("!")[0];
        // With:
        String externalForm = packageFolderURL.toExternalForm();
        String jarUri = externalForm.substring(0, externalForm.lastIndexOf('!'));
    
    
        JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
        String rootEntryName = jarConn.getEntryName();
        int rootEnd = rootEntryName.length()+1;
        // ...
    

    这允许包PackageInternalsFinder 将带有完整URI 的CustomJavaFileObject 返回到嵌入式spring jar(在BOOT-INF/lib 下)中的类,然后使用spring boot jar URI handler 解析,其注册方式与in this answer 解释类似。 URI 处理应该通过 spring boot 自动发生。

    【讨论】:

    • 谢谢,我使用的是 Java 10,所以我猜你的解决方案不适用于我?我可能会考虑升级到 11
    • 这个答案应该适用于 Java 9 到 11。所以我的答案应该适用于你的情况。应该不需要升级到 Java 11。
    • 你确定它不适合 java 10 吗?我应该试一试吗?
    • 值得一试。应该没有问题!我修改了我的答案,以明确它可以在 Java 9+ 上运行。
    • 太好了,很快就会试一试
    猜你喜欢
    • 1970-01-01
    • 2012-11-15
    • 1970-01-01
    • 2015-11-10
    • 2014-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-02
    相关资源
    最近更新 更多