gpt4 book ai didi

java - 实现自定义 ClassLoader 以扫描/WEB-INF/classes 目录

转载 作者:行者123 更新时间:2023-11-28 21:53:37 25 4
gpt4 key购买 nike

为了减少对外部库的依赖(并且主要作为学习练习),我决定将 ServletContextListener 添加到我正在开发的教学网络应用程序中。它将通过扫描“WEB-INF/classes”目录建立一个具有特定注释的类名(存储为字符串)注册表。

作为其中的一部分,我编写了一个自定义 ClassLoader,我可以经常丢弃它,以防止在加载上下文时通过滥用我开始使用的 WebappClassLoader 来填充 permgen。

不幸的是,我得到了一个讨厌的 java.lang.NoClassDefFoundError: javax/websocket/server/ServerEndpointConfig$Configurator尝试加载我的 ServerEndpointConfigurators 之一时出现异常。

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 实现的影响。

这是我正在尝试做的一般流程:
  • 为我尝试加载的类名获取一个锁,以确保其他线程(如果我尝试在将来的某个时间点并行执行这个 ClassLoader)不要尝试加载同一个类两次。
  • 检查我是否已经使用 findLoadedclass(); 加载了这个类;
  • 如果尚未加载,请尝试从我的“WEB-INF/classes”目录中拉入该类。
  • 如果失败,委托(delegate)给父类加载器。
  • 如果这仍然失败,请炸毁( throw )。

  • 我可以保证在扫描类文件后我正确地传递了类名 - 如果我用 Class.forName( className ) 替换 MetascanClassLoader.load( className ) 调用,这将非常有效,但如前所述,我不想锤击permgen。

    似乎只有在尝试加载包含对只能与 Tomcat 打包的类的引用的类时,它才会崩溃。 Java SE 类完全没有问题。

    让我知道您是否还碰巧注意到我正在做的任何特别阴险/令人反感的事情,除了导致它不起作用。

    更新 :似乎使用父类(super class)的默认构造函数将父类加载器设置为系统类加载器,这将解释在 Tomcat 中找到的缺失类。

    我在构造函数中添加了以下行作为第一行:
    super( Thread.currentThread().getContextClassLoader() );

    不幸的是,我仍然遇到问题,因为我的 ClassLoader 返回的所有 Class 对象现在都是空的,除了类名之外没有任何信息。 (我使用 Eclipse 检查了内部字段以发现这一点。)

    更多信息 :通过对象检查,Java SE 类仍在正确加载。当我检查一个存在于 WEB-INF\classes 中的类时,这是我在调用 loadClass() 后的任何时候尝试检查类对象时得到的那种行为(我已将包名称隐藏到防止共享不必要的项目信息)。
    Eclipse inspection .

    我还尝试确保通过将 loadClass(name,resolve) 硬编码为 true 来调用 resolveClass(),但这并没有什么区别。

    再次更新 : 感谢 Holger 在下面的精彩“slap-with-a-trout”时刻,我非常愚蠢地认为我可以推断出 Class 中私有(private)变量的含义。返回的对象。

    我扔了几个 System.out.print() s 里面我的 ClassLoader (当然是 synchronized - 我第一次尝试不同步时非常困惑!)我在 loadClass() 的 return 语句之前卡住了以下行
    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();

    结果:
    org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;HAS_ANNOTATION:false

    哎呀!但后来它击中了我。在下面检查我的答案。

    最佳答案

    我记得读过一些关于 java.lang.Class 的有趣内容今天的平等当我回去尝试调试这个东西时完全跳过了我的想法。 Jon Skeet mentioned in the answer to another question :

    Yes, that code is valid - if the two classes have been loaded by the same classloader. If you want the two classes to be treated as equal even if they've been loaded by different classloaders, possibly from different locations, based on the fully-qualified name, then just compare fully-qualified names instead.

    Note that your code only considers an exact match, however - it won't provide the sort of "assignment compatibility" that (say) instanceof does when seeing whether a value refers to an object which is an instance of a given class. For that, you'd want to look at Class.isAssignableFrom.


    java.lang.Class不覆盖 java.lang.Object.equals() , 每个 Class对象保留对其 ClassLoader 的引用, 即使两个 Class es 具有相同的类、数据和名称,如果它们来自两个不同的 ClassLoaders,它们将不相等.那么这在这里如何应用呢?
    有问题的代码行是
    if( clazz.getAnnotation( MyAnnotationOne.class ) != null )
    调用 clazz.getAnnotations() ,如上在问题的最终更新中将列出具有完全限定类名的注释,该类名等于 MyAnnotationOne.class 引用的类的完全限定类名上面 if 语句中的文字。但是,我猜 clazz.getAnnotation()使用一些内部 equals()调用以检查注释类是否相同。 MyAnnotationOne.class 引用的类由自定义 ClassLoader 加载的类加载器(在大多数情况下将是它的父级)。但是, MyAnnotationOne附加到 clazz 的类由自定义 ClassLoader 加载本身。因此, equals()方法返回 false , 我们得到 null背部。
    非常感谢 Holger 将我推回正确的方向并最终让我得到答案。

    关于java - 实现自定义 ClassLoader 以扫描/WEB-INF/classes 目录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19908634/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com