2

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

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

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

我已经关注了这篇文章,它假设可以解决这个特定问题,但我UnsupportedOperationException来自方法

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

的界面JavaFileManager

我在这里遇到了另一种可能的解决方案,但我不清楚完整的实现。

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

我目前正在使用 Java 10.0.2。

4

1 回答 1

3

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

如果您实际上不想使用大于 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 的评论一样,而不是原始示例中的第一个。

 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 处理程序解析,该处理程序的注册方式与本答案中解释的类似。URI 处理应该通过 spring boot 自动发生。

于 2018-12-31T12:12:23.133 回答