为了减少对外部库的依赖(主要是作为学习练习),我决定将ServletContextListener添加到我正在开发的教学webapp中。它将通过扫描“WEB-INF / classes”目录来构建一个具有特定注释的类名注册表(存储为字符串)。
作为其中的一部分,我编写了一个自定义的ClassLoader,我可以经常抛弃它,以防止在加载上下文时通过滥用我开始使用的WebappClassLoader来填充permgen。
不幸的是,当我尝试加载一个ServerEndpointConfigurators时,我遇到了一个讨厌的java.lang.NoClassDefFoundError: javax/websocket/server/ServerEndpointConfig$Configurator
异常。
public class MetascanClassLoader extends ClassLoader
{
private final String myBaseDir;
public MetascanClassLoader( final String baseDir )
{
if( !baseDir.endsWith( File.separator ) )
{
myBaseDir = baseDir + File.separator;
}
else
{
myBaseDir = baseDir;
}
}
@Override
protected Class<?> loadClass( final String name, final boolean resolve )
throws ClassNotFoundException
{
synchronized( getClassLoadingLock( name ) )
{
Class<?> clazz = findLoadedClass( name );
if (clazz == null)
{
try
{
final byte[] classBytes =
getClassBytesByName( name );
clazz = defineClass(
name, classBytes, 0, classBytes.length );
}
catch (final ClassNotFoundException e)
{
if ( getParent() != null )
{
clazz = getParent().loadClass( name );
}
else
{
throw new ClassNotFoundException(
"Could not load class from MetascanClassloader's " +
"parent classloader",
e );
}
}
}
if( resolve )
{
resolveClass( clazz );
}
return clazz;
}
}
private byte[] getClassBytesByName( final String name )
throws ClassNotFoundException
{
final String pathToClass =
myBaseDir + name.replace(
'.', File.separatorChar ) + ".class";
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try( final InputStream stream = new FileInputStream( pathToClass ) )
{
int b;
while( ( b = stream.read() ) != -1 )
{
baos.write( b );
}
}
catch( final FileNotFoundException e )
{
throw new ClassNotFoundException(
"Could not load class in MetascanClassloader.", e );
}
catch( final IOException e )
{
throw new RuntimeException( e );
}
return baos.toByteArray();
}
}
部分代码受默认ClassLoader实现的影响。
以下是我正在尝试做的一般流程:
我可以保证在扫描类文件后正确传递类名 - 如果我用Class.forName(className)替换MetascanClassLoader.load(className)调用,这非常有效,但如前所述,我不知道我想锤击permgen。
在尝试加载包含对只能与Tomcat打包的类的引用的类时,它才会出现问题。它完全没有Java SE类的问题。
如果你碰巧发现任何特别阴险/令人反感的事情,请告诉我,除了让它不起作用之外我还要做些什么。
UPDATE :似乎使用超类的默认构造函数将父类加载器设置为系统类加载器,这将解释在Tomcat中找到的缺失类。
我在构造函数的第一行添加了以下行:
super( Thread.currentThread().getContextClassLoader() );
不幸的是,我仍然遇到问题,因为我的ClassLoader返回的所有Class对象现在都是空的,除了类名之外没有任何信息。 (我使用Eclipse检查内部字段以发现它。)
更多信息:通过对象检查,Java SE类仍然正确加载。当我检查生活在WEB-INF \ classes中的一个类时,这是我在调用loadClass()后在任何时候检查类对象时得到的行为(我隐藏了包名称以防止共享不必要的项目信息)。
我还尝试确保通过将loadClass(name,resolve)的硬编码解析为true来调用resolveClass(),但这没有区别。
再次更新:感谢Holger在下面出色的“啪嗒啪嗒啪嗒啪嗒啪”的一刻,我觉得我可以推断出{{1中的私有变量的含义 - 我是非常愚蠢的正在返回的对象。
我在我的Class
(System.out.print()
当中扔了几个ClassLoader
s - 我第一次尝试没有同步时非常凌乱!)我在之前的那一行卡住了以下一行返回synchronized
loadClass()
这给了我期待的结果,打印出了以下内容:
System.out.print( clazz.getName() + " annotations:" );
for( final Annotation a : clazz.getAnnotations() )
{
System.out.print( " " + a.annotationType().getName() + ";" );
}
System.out.println();
但是等一下 - 它有效吗?
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;
结果:
System.out.print( clazz.getName() + " annotations:" );
for( final Annotation a : clazz.getAnnotations() )
{
System.out.print( " " + a.annotationType().getName() + ";" );
}
System.out.print( "HAS_ANNOTATION:" );
if( clazz.getAnnotation( MyAnnotationOne.class ) != null )
{
System.out.print( "true" );
}
else
{
System.out.print( "false" );
}
System.out.println();
糟糕!但后来它袭击了我。检查下面的答案。
答案 0 :(得分:0)
我记得今天阅读有关java.lang.Class
平等的一些有趣内容,当我回去试图调试这个东西时完全跳过了我的脑海。 Jon Skeet mentioned in the answer to another question:
是的,该代码有效 - 如果这两个类已由同一个类加载器加载。如果你希望这两个类被视为相等,即使它们已被不同的类加载器加载,可能来自不同的位置,基于完全限定的名称,那么只需比较完全限定的名称。
请注意,您的代码只考虑完全匹配,但是 - 它不会提供(当然)instanceof在查看值是否指向作为给定类的实例的对象时执行的“赋值兼容性”类型。为此,您需要查看Class.isAssignableFrom。
由于java.lang.Class
未覆盖java.lang.Object.equals()
,并且每个Class
对象都会保留对其ClassLoader
的引用,即使两个Class
具有相同的类,数据和名称,如果它们来自两个不同的ClassLoaders
则不相等。那么这在哪里适用?
有问题的代码行是
if( clazz.getAnnotation( MyAnnotationOne.class ) != null )
如上所述,在问题的最后更新中调用clazz.getAnnotations()
将列出一个带有完全限定类名的注释,该类名等于{{1}引用的类的完全限定类名。上面的if语句中的文字。但是,我猜测MyAnnotationOne.class
使用一些内部clazz.getAnnotation()
调用来检查注释类是否相同。
equals()
引用的类由自定义MyAnnotationOne.class
的classLoader加载(在大多数情况下,它将是其父级)。但是,附加到ClassLoader
的{{1}}类由自定义MyAnnotationOne
本身加载。因此,clazz
方法返回ClassLoader
,我们返回equals()
。
非常感谢Holger将我推向了正确的方向并最终让我得到答案。