自定义classloader时两个groovy 类互相导入导致循环编译错误
Two groovy classes import each other caused loop compile error when customised classloader
我自定义了 Groovy class 加载程序,用于从文件系统中的目录加载 classes。
现在我有 groovy class 下面的例子,
ICommentService.groovy
public interface ICommentService {
public void a();
}
CommentService.groovy
public class CommentService implements ICommentService {
public void a() {
//doing something...
}
}
IMomentService.groovy
public interface IMomentService {
public void b();
}
MomentService.groovy
import ICommentService;
import CommentService;
public class MomentService implements IMomentService {
public void b() {
ICommentService commentService = new CommentService();
commentService.a();
//doing something
}
}
我自定义的 classloader 代码如下,
public class MyGroovyClassLoader extends GroovyClassLoader {
private long version;
private HashMap<String, ClassHolder> classCache;
private HashSet<String> pendingGroovyClasses;
public MyGroovyClassLoader(ClassLoader parentClassLoader,
CompilerConfiguration cc) {
super(parentClassLoader, cc);
classCache = new HashMap<>();
}
public Class<?> parseGroovyClass(String key, File classFile)
throws CoreException {
ClassHolder holder = classCache.get(key);
if(holder != null && holder.getParsedClass() != null) {
LoggerEx.info(TAG, "Load groovy class " + key
+ " from cache");
return holder.getParsedClass();
}
try {
Class<?> parsedClass = parseClass(classFile);
if (parsedClass != null) {
holder = new ClassHolder();
holder.parsedClass = parsedClass;
classCache.put(key, holder);
}
LoggerEx.info(TAG, "Parse groovy class " + key
+ " successfully");
return parsedClass;
} catch (Throwable e) {
e.printStackTrace();
throw new CoreException(
ChatErrorCodes.ERROR_GROOVY_PARSECLASS_FAILED,
"Parse class " + classFile + " failed, "
+ e.getMessage());
}
}
@Override
public Class loadClass(String name, boolean lookupScriptFiles,
boolean preferClassOverScript) throws ClassNotFoundException,
CompilationFailedException {
// TODO Auto-generated method stub
Class<?> loadedClass = null;
if(pendingGroovyClasses.contains(name)) {
String key = name.replace(".", "/") + ".groovy";
try {
loadedClass = parseGroovyClass(key, new File(path + key));
if(loadedClass != null)
return loadedClass;
} catch (CoreException e) {
e.printStackTrace();
LoggerEx.error(TAG, "parse groovy class failed while load class, " + e.getMessage());
}
}
try {
loadedClass = super.loadClass(name, lookupScriptFiles,
preferClassOverScript);
} catch (ClassNotFoundException e) {
}
return loadedClass;
}
public ClassHolder getClass(String classPath) {
return classCache.get(classPath);
}
public long getVersion() {
return version;
}
public String toString() {
return MyGroovyClassLoader.class.getSimpleName() + "#" + version;
}
}
现在,当我们解析目录中的那些 groovy 文件时。
然后我们发现一个无限循环日志如下,
Parse groovy class ICommentService.groovy successfully
Parse groovy class IMomentService.groovy successfully
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
问题是两个class互相导入,可以理解为什么会出现循环。但是 java classloader 似乎可以很好地处理这个问题。那我们如何解决Groovy中的这个问题呢?
请帮忙!非常感谢!
对于编译器完成的内部查找,标志 lookupScriptFiles 为 false 以完全避免此问题。您应该通过 set/getResourceLoader 使用 GroovyResourceLoader
或在 GroovyClassLoader 上覆盖 getResource(String)
而不是覆盖 loadClass
(此方法从资源加载器调用)。这样编译器将能够自己查找源代码并将其自动添加到编译队列中。
我自定义了 Groovy class 加载程序,用于从文件系统中的目录加载 classes。
现在我有 groovy class 下面的例子,
ICommentService.groovy
public interface ICommentService {
public void a();
}
CommentService.groovy
public class CommentService implements ICommentService {
public void a() {
//doing something...
}
}
IMomentService.groovy
public interface IMomentService {
public void b();
}
MomentService.groovy
import ICommentService;
import CommentService;
public class MomentService implements IMomentService {
public void b() {
ICommentService commentService = new CommentService();
commentService.a();
//doing something
}
}
我自定义的 classloader 代码如下,
public class MyGroovyClassLoader extends GroovyClassLoader {
private long version;
private HashMap<String, ClassHolder> classCache;
private HashSet<String> pendingGroovyClasses;
public MyGroovyClassLoader(ClassLoader parentClassLoader,
CompilerConfiguration cc) {
super(parentClassLoader, cc);
classCache = new HashMap<>();
}
public Class<?> parseGroovyClass(String key, File classFile)
throws CoreException {
ClassHolder holder = classCache.get(key);
if(holder != null && holder.getParsedClass() != null) {
LoggerEx.info(TAG, "Load groovy class " + key
+ " from cache");
return holder.getParsedClass();
}
try {
Class<?> parsedClass = parseClass(classFile);
if (parsedClass != null) {
holder = new ClassHolder();
holder.parsedClass = parsedClass;
classCache.put(key, holder);
}
LoggerEx.info(TAG, "Parse groovy class " + key
+ " successfully");
return parsedClass;
} catch (Throwable e) {
e.printStackTrace();
throw new CoreException(
ChatErrorCodes.ERROR_GROOVY_PARSECLASS_FAILED,
"Parse class " + classFile + " failed, "
+ e.getMessage());
}
}
@Override
public Class loadClass(String name, boolean lookupScriptFiles,
boolean preferClassOverScript) throws ClassNotFoundException,
CompilationFailedException {
// TODO Auto-generated method stub
Class<?> loadedClass = null;
if(pendingGroovyClasses.contains(name)) {
String key = name.replace(".", "/") + ".groovy";
try {
loadedClass = parseGroovyClass(key, new File(path + key));
if(loadedClass != null)
return loadedClass;
} catch (CoreException e) {
e.printStackTrace();
LoggerEx.error(TAG, "parse groovy class failed while load class, " + e.getMessage());
}
}
try {
loadedClass = super.loadClass(name, lookupScriptFiles,
preferClassOverScript);
} catch (ClassNotFoundException e) {
}
return loadedClass;
}
public ClassHolder getClass(String classPath) {
return classCache.get(classPath);
}
public long getVersion() {
return version;
}
public String toString() {
return MyGroovyClassLoader.class.getSimpleName() + "#" + version;
}
}
现在,当我们解析目录中的那些 groovy 文件时。 然后我们发现一个无限循环日志如下,
Parse groovy class ICommentService.groovy successfully
Parse groovy class IMomentService.groovy successfully
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
Load groovy class ICommentService.groovy from cache
Load groovy class IMomentService.groovy from cache
问题是两个class互相导入,可以理解为什么会出现循环。但是 java classloader 似乎可以很好地处理这个问题。那我们如何解决Groovy中的这个问题呢?
请帮忙!非常感谢!
对于编译器完成的内部查找,标志 lookupScriptFiles 为 false 以完全避免此问题。您应该通过 set/getResourceLoader 使用 GroovyResourceLoader
或在 GroovyClassLoader 上覆盖 getResource(String)
而不是覆盖 loadClass
(此方法从资源加载器调用)。这样编译器将能够自己查找源代码并将其自动添加到编译队列中。