我正在开发一个eclipse插件。当插件运行时(例如在eclipse的调试实例中),它会从调试工作区中的所有java项目中加载某些类。反射用于此目的。为了更好地了解我在这里做的是一些代码:
for (IPath outDir : _outputDirs)
{
IPath fullTestPackageDir = outDir.append(testPackageDir);
File dir = fullTestPackageDir.toFile();
if (dir.exists() && dir.isDirectory())
{
for (File classFile : dir.listFiles())
{
String binaryClassName = packageName + "." + FileUtils.getNameWithoutExtension(classFile);
Class<ITest> testClass = tryLoadClass(binaryClassName, ITest.class);
if (testClass != null)
{
testClasses.add(testClass);
}
}
}
}
以下是我在上面使用的tryLoadClass
的实现,正如评论中所要求的那样(根本没有多少魔法)。 _urlClassLoader是从URL
的数组(调试工作区中的java-projects的输出文件夹)创建的,并且当前线程的上下文ClassLoader是父级。
@SuppressWarnings("unchecked")
private <T> Class<T> tryLoadClass(String binaryClassName, Class<T> baseType)
{
try
{
Class<?> testClass = _urlClassLoader.loadClass(binaryClassName);
if (!baseType.isAssignableFrom(testClass))
return null;
return (Class<T>)testClass;
}
catch (ClassNotFoundException e)
{
System.err.println("class not found");
return null;
}
}
稍后,将实例化这些类并调用函数(run
):
Class<ITest> testClass = testClasses.get(i);
try
{
ITest test = testClass.getConstructor(IRuntime.class).newInstance(_runtime);
test.run();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e)
{
e.printStackTrace();
}
在插件的包中有几个实用程序类,例如MyUtil
,由插件导出,可以由上面加载的类引用。确切地说,MyUtil
函数中引用了run
。
上面的代码一切正常。但是如果我将test.run()
移动到另一个Thread中,我会得到MyUtil
的ClassNotFoundException。这是代码(它几乎与上面相同,只替换了第6行)。 ITest
继承自Runnable
:
Class<ITest> testClass = testClasses.get(i);
try
{
ITest test = testClass.getConstructor(IRuntime.class).newInstance(_runtime);
Thread thread = new Thread(test, "TestThread");
thread.start();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e)
{
e.printStackTrace();
}
我已经尝试了很多东西,例如获取当前线程的ContextClassLoader
并将其传递给新线程,但不是运气。我发现的是我可以显式地使用当前线程的ContextClassLoader
加载MyUtil
,但仅限于在原始线程中使用它时。如果我将该ClassLoader传递给新线程,它将停止工作。
我认为我的问题是我对ClassLoader如何工作有一些错误的理解......
以下是有关例外的更多详细信息:
Caused by: java.lang.ClassNotFoundException: de.my.company.MyPlugin
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
... 2 more
因为有一个关于如何加载“de.my.company.MyPlugin”的问题:它只是在bundle / plugin中,这是调试工作区中java项目所需要的,因此我认为它是在“ base“classpath(这里不知道正确的术语......)
/编辑:
我刚才意识到我没有提到可能很重要的东西:test.run()
(以及启动新线程)是在Eclipse Job
的上下文中完成的。但我想我已经算出来了:当我删除Job
- 上下文(直接从Eclipse命令的Handler中发出代码)时,问题仍然存在。但是,如果ITest
实现正常实例化(即在eclipse调试实例中;我在一个简单的main()
函数中测试它),则问题不存在。
我认为问题是由于我的自定义_urlClassLoader
用于加载ITest
。因为Java尝试使用在开头(afaik)加载容器类的类加载器加载任何引用的类,所以我的_urlClassLoader将再次被使用。但是,_urlClassLoader本身无法加载所请求的类(它只在其“classpath”上有其他java项目的输出文件夹)。相反,必须使用其父级。如果我理解了一切正确,这应该自动发生,但正是这部分不再适用于新线程......
答案 0 :(得分:1)
我没有深入研究OSGi加载类的方法,但我知道标准机制足以陈述以下内容:
ClassLoader
Thread
)不会改变
URLClassLoader
实施的父加载程序的委托无条件地发生因此,如果这些部分没有改变,那么它就是OSGi框架提供的父加载器,它改变了它的行为。我发现this blog entry描述了一种适合观察行为的机制:
上下文查找器
上下文查找器是一种ClassLoader,由Equinox Framework安装为默认的上下文类加载器。调用时,它会在Java执行堆栈中搜索除系统类加载器之外的类加载器。
因此,当您使用Thread
动态加载的Runnable
开始新的URLClassLoader
时,Thread
的堆栈跟踪中没有可以捆绑的包用作上下文。
一个简单的解决方法是将您的包中的代码添加到新Thread
的堆栈跟踪中,即更改
Thread thread = new Thread(test, "TestThread");
到
Thread thread = new Thread(new Runnable() {
public void run() { test.run(); }
}, "TestThread");
当然,test
必须更改为final
这样,源自Eclipse插件的调用者(匿名内部类)位于新Thread
堆栈的顶部。由于您的URLClassLoader
的父级已经初始化为OSGi类加载器,因此不需要设置上下文类加载器(除非在加载的代码中使用线程上下文加载器进行一些动态加载)。