Java 实现自定义类加载器扫描/WEB-INF/classes目录
为了减少对外部库的依赖(主要是作为学习练习),我决定将ServletContextListener添加到我正在开发的教学Web应用程序中。它将通过扫描“WEB-INF/classes”目录来建立具有特定注释的类名(存储为字符串)的注册表 作为这项工作的一部分,我编写了一个自定义类加载器,我可以经常丢弃它,以防止在加载上下文时滥用我开始使用的WebappClassLoader来填充permgen 不幸的是,我在尝试加载我的一个ServerEndpointConfigurator时遇到了一个严重的Java 实现自定义类加载器扫描/WEB-INF/classes目录,java,tomcat,classloader,Java,Tomcat,Classloader,为了减少对外部库的依赖(主要是作为学习练习),我决定将ServletContextListener添加到我正在开发的教学Web应用程序中。它将通过扫描“WEB-INF/classes”目录来建立具有特定注释的类名(存储为字符串)的注册表 作为这项工作的一部分,我编写了一个自定义类加载器,我可以经常丢弃它,以防止在加载上下文时滥用我开始使用的WebappClassLoader来填充permgen 不幸的是,我在尝试加载我的一个ServerEndpointConfigurator时遇到了一个严重的j
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();
}
}
不幸的是,我仍然遇到问题,因为我的类加载器返回的所有类对象现在都是空的,除了类名之外没有任何信息。(我使用Eclipse检查了内部字段以发现这一点。)
更多信息:通过对象检查,Java SE类仍在正确加载。当我检查WEB-INF\classes中的一个类时,这就是我在调用loadClass()后的任何时候尝试检查class对象时得到的行为(我隐藏了包名以防止共享不必要的项目信息)。
我还尝试通过硬编码将loadClass的resolve(名称,resolve)解析为true来确保调用resolveClass(),但这并没有什么区别
再次更新:多亏了下面霍尔格出色的“用鳟鱼拍打”时刻,我非常愚蠢地认为我可以推断返回的类
对象中私有变量的含义
我在我的类加载器中抛出了一些System.out.print()
s(已同步的当然了-我第一次尝试不同步时非常混乱!)我在loadClass()的返回语句之前坚持了下面的一行
这让我得到了我所期望的结果,打印出了如下内容:
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();
结果是:
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;HAS_ANNOTATION:false
哎呀!但是我突然想到了。查看下面的答案。我记得今天读到了一篇关于java.lang.Class
平等的有趣文章,当我再次尝试调试这篇文章时,我完全忘记了这一点:
是的,如果这两个类是由同一个类加载器加载的,那么该代码是有效的。如果您希望两个类被视为相等的,即使它们是由不同的类加载器(可能来自不同的位置)基于完全限定名加载的,那么只需比较完全限定名即可
请注意,您的代码只考虑精确匹配,但它不会提供instanceof在查看值是否引用作为给定类实例的对象时所提供的那种“赋值兼容性”。为此,您需要查看Class.isAssignableFrom
由于java.lang.Class
不会覆盖java.lang.Object.equals()
,并且每个Class
对象保留对其ClassLoader
的引用,即使两个Class
具有相同的类、数据和名称,如果它们来自两个不同的ClassLoader
,它们也不会相等。那么这在这里是如何应用的呢
有问题的代码行是
if( clazz.getAnnotation( MyAnnotationOne.class ) != null )
在问题的最终更新中调用clazz.getAnnotations()
,将列出具有完全限定类名的注释,该类名等于上述if语句中的MyAnnotationOne.class
文本引用的类的完全限定类名。但是,我猜clazz.getAnnotation()
使用一些内部equals()
调用来检查注释类是否相同
MyAnnotationOne.class
引用的类由自定义的类加载器
的类加载器加载(在大多数情况下,它将是其父类)。但是,附加到clazz
的MyAnnotationOne
类由自定义ClassLoader
本身加载。结果,equals()
方法返回false
,然后返回null
非常感谢Holger将我推回到正确的方向,并最终让我找到了答案。“(我使用Eclipse检查了内部字段以发现这一点。)“这并不意味着什么。在OOP中进行封装是有原因的。仅仅查看对象的私有字段并不能告诉您关于语义的任何信息。我敢打赌,如果你在类实例上调用,例如,getDeclaredMethods()
,而不是用调试器偷看,事情看起来会完全不同。评论棒极了。偷窥发生了,对于其他类,这些字段似乎被设置了,我错误地认为这与问题有关。我将用我的新发现更新这个问题。据我所知,这些字段可以存储以前挖掘的值。
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;HAS_ANNOTATION:false
if( clazz.getAnnotation( MyAnnotationOne.class ) != null )