Compile java class in runtime with dependencies to nested jar

守給你的承諾、 提交于 2021-02-08 06:55:45

问题


In a spring-boot app I'm doing the following in runtime:

  1. Generating a java class
  2. Compiling it
  3. Accessing some static fields of the compiled class using reflection.

I've based my code on this post and got a problem compiling my generated class in runtime. When running in the IDE compilation works just fine but when runing from a spring-boot jar compilation fails saying symbols are missing or some package does not exist. The class I'm compiling has dependencies to other classes that reside in a jar under \BOOT-INF\lib\ and it seems the compiler fails to load those classes using the existing classLoader.

I've followed this post which suppose to address this specific problem but I got UnsupportedOperationException coming from method

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

of interface JavaFileManager.

I've encountered another possible solution given here but I'm not clear exactly with the full implementation.

This seems like a well known issue when compiling a class in runtime, is there any clear solution for that?

Update: I'm currently using java 10.0.2.


回答1:


Although you haven't mentioned it explicitly I think you are running a version of Java with modules (JDK 9+), but the guides you have been following are for earlier versions starting from Java 6. This is why you are getting the error about unsupported listLocationsForModules because the JDK developers retrofitted the FileManager with with default methods that throw UnsupportedOperationException.

If you don't actually want to use a version of Java greater than 8, I would stick with JDK8 it will be much easier!

I'll continue assuming you do want to use Java 9 and above (tested my code in Java 11) however:

For handling modules it is sufficient your file manager to delegate to the standard file manager:

@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);
}

I also found it necessary to modify Atamur's code to check explicitly for the base java module (so that we can resolve java.lang in Java 9+!) and delegate to the standard file manager as you would have for the platform class path in previous versions:

@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();

}

Updates

Some other points:

Extracting embedded spring boot classes:

Get the jarUri by looking for the last index of '!' in each packageFolderURL as in Taeyun Kim's comment and not the first as in the original example.

 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;
    // ...

This allows package PackageInternalsFinder to return CustomJavaFileObject with full URIs to classes in embedded spring jars (under BOOT-INF/lib) which are then resolved with spring boot jar URI handler which is registered in similar way to as explained in this answer. The URI handling should just happen automatically through spring boot.



来源:https://stackoverflow.com/questions/53936466/compile-java-class-in-runtime-with-dependencies-to-nested-jar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!