使用 动态编译在 运行期根据配置文件生成java代码 并且编译为class 加载到 classloader中 的玩法已经用了一年多了,但是一直有个坑就是 在编译Java class的时候需要 提取依赖jar包到 服务器的某个目录中,然后加上- classPath 参数才可运行成功。
于是我就想在想有没有一种办法可以让程序编译的时候去springboot boot info下面去找依赖。
众所周知,在IDE中运行springboot 程序 类加载器是JDK自带的,类加载器已经完成了依赖jar的加载,所以编译是没问题的,但是springboot 用的是springboot自己写的LaunchedURLClassLoader ,我在想是不是换掉相关的classloader 就可以实现了。于是重写了以下代码。
/**
* java 文件管理器 主要用来 重新定义class loader
*/
class SpringJavaFileManager extends JavacFileManager {
public SpringJavaFileManager(Context context, boolean b, Charset charset) {
super(context, b, charset);
}
@Override
public ClassLoader getClassLoader(Location location) {
nullCheck(location);
Iterable var2 = this.getLocation(location);
if (var2 == null) {
return null;
} else {
ListBuffer var3 = new ListBuffer();
Iterator var4 = var2.iterator();
while (var4.hasNext()) {
File var5 = (File) var4.next();
try {
var3.append(var5.toURI().toURL());
} catch (MalformedURLException var7) {
throw new AssertionError(var7);
}
}
return this.getClassLoader((URL[]) var3.toArray(new URL[var3.size()]));
}
}
protected ClassLoader getClassLoader(URL[] var1) {
ClassLoader var2 = this.getClass().getClassLoader();
if (this.classLoaderClass != null) {
try {
Class loaderClass = Class.forName("org.springframework.boot.loader.LaunchedURLClassLoader");
Class[] var4 = new Class[]{URL[].class, ClassLoader.class};
Constructor var5 = loaderClass.getConstructor(var4);
return (ClassLoader) var5.newInstance(var1, var2);
} catch (Throwable var6) {
}
}
return new URLClassLoader(var1, var2);
}
}
这样使用:
public static SpringJavaFileManager getStandardFileManager(DiagnosticListener<? super JavaFileObject> var1, Locale var2, Charset var3) {
Context var4 = new Context();
var4.put(Locale.class, var2);
if (var1 != null) {
var4.put(DiagnosticListener.class, var1);
}
PrintWriter var5 = var3 == null ? new PrintWriter(System.err, true) : new PrintWriter(new OutputStreamWriter(System.err, var3), true);
var4.put(Log.outKey, var5);
return new SpringJavaFileManager(var4, true, var3);
}
private static Map<String, byte[]> compile(String className, String javaStr) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = getStandardFileManager(null, null, null);
发现没啥用,看来只改此处还是不行,于是去国外论坛翻相关知识点。发现一个大神说要从JavaFileManager入手,因为Java编译器是通过JavaFileManager来加载相关依赖类的,于是又开动 重写JavaFileManager,使用到了springboot的 jarFile 来读取嵌套jar。
/**
* 内存Java文件管理器
*/
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// compiled classes in bytes:
final Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
final Map<String, List<JavaFileObject>> classObjectPackageMap = new HashMap<>();
private JavacFileManager javaFileManager;
MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
this.javaFileManager = javaFileManager;
}
public Map<String, byte[]> getClassBytes() {
return new HashMap<String, byte[]>(this.classBytes);
}
@Override
public void flush() throws IOException {
}
@Override
public void close() throws IOException {
classBytes.clear();
}
public List<JavaFileObject> getLibJarsOptions(String packgeName) {
List<JavaFileObject> result = new ArrayList<>();
try {
String jarBaseFile = MemoryClassLoader.getPath();
JarFile jarFile = new JarFile(new File(jarBaseFile));
List<JarEntry> entries = jarFile.stream().filter(jarEntry -> {
return jarEntry.getName().endsWith(".jar");
}).collect(Collectors.toList());
JarFile libTempJarFile = null;
List<JarEntry> tempEntries = null;
for (JarEntry entry : entries) {
tempEntries = new ArrayList<>();
libTempJarFile = jarFile.getNestedJarFile(jarFile.getEntry(entry.getName()));
Enumeration<JarEntry> tempEntriesEnum = libTempJarFile.entries();
while(tempEntriesEnum.hasMoreElements()){
JarEntry jarEntry = tempEntriesEnum.nextElement();
String classPath = jarEntry.getName().replace("/", ".");
if(!classPath.endsWith(".class") || jarEntry.getName().lastIndexOf("/") == -1){
continue;
}
else if(classPath.substring(0,jarEntry.getName().lastIndexOf("/")).equals(packgeName)){
tempEntries.add(jarEntry);
}
}
for (JarEntry tempEntry : tempEntries) {
result.add(new MemorySpringBootInfoJavaClassObject(tempEntry.getName().replace("/", ".").replace(".class",""),
new URL(libTempJarFile.getUrl(),tempEntry.getName()),javaFileManager));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
基本上通过修改以上2个点就搞定了。 -- 代码有点乱,还未整理。
开源项目地址:https://gitee.com/fhs-opensource/fhs-framework
fhs framework qq群:976278956