Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/341.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 实现自定义类加载器扫描/WEB-INF/classes目录_Java_Tomcat_Classloader - Fatal编程技术网

Java 实现自定义类加载器扫描/WEB-INF/classes目录

Java 实现自定义类加载器扫描/WEB-INF/classes目录,java,tomcat,classloader,Java,Tomcat,Classloader,为了减少对外部库的依赖(主要是作为学习练习),我决定将ServletContextListener添加到我正在开发的教学Web应用程序中。它将通过扫描“WEB-INF/classes”目录来建立具有特定注释的类名(存储为字符串)的注册表 作为这项工作的一部分,我编写了一个自定义类加载器,我可以经常丢弃它,以防止在加载上下文时滥用我开始使用的WebappClassLoader来填充permgen 不幸的是,我在尝试加载我的一个ServerEndpointConfigurator时遇到了一个严重的j

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

作为这项工作的一部分,我编写了一个自定义类加载器,我可以经常丢弃它,以防止在加载上下文时滥用我开始使用的WebappClassLoader来填充permgen

不幸的是,我在尝试加载我的一个ServerEndpointConfigurator时遇到了一个严重的
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 )