Class<?>.isAnnotationPresent returns 注释为 false class

Class<?>.isAnnotationPresent returns false for annotated class

我有自己的注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}

和class:

package com.ltp.analog.test;

import com.ltp.analog.core.annotation.Component;

@Component
public class TestOne {

    public void test(){
        System.out.println("TEST ONE");
    }

}

还实现了自定义类加载器:


package com.ltp.analog.reflection;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class AnalogClassLoader extends ClassLoader{

    @Override
    public Class findClass(String name) {
        Class cl = findLoadedClass(name);

        if(cl != null){
            return cl;
        }

        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(
                fileName.replaceAll("[.]", "/") + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }

}

ReflectionUtils class必须递归加载包中的所有class:

package com.ltp.analog.reflection;

import com.ltp.analog.Testing;
import com.ltp.analog.reflection.qualifier.ClassQualifier;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

public class ReflectionUtils {

    private static final AnalogClassLoader cl = new AnalogClassLoader();

    public static List<Class> getClassesInPackageRecursively(String packName){
        List<String> packs = getSubpackagesRecursively(packName);

        List<Class> result = new LinkedList<>();

        packs.forEach(pack -> result.addAll(getClassesInPackage(pack)));

        return result.stream().distinct().collect(Collectors.toList());
    }

    public static List<Class> getClassesInPackage(String packName){
        if(packName == null || packName.isEmpty()){
            return List.of();
        }

        URL url = ClassLoader.getSystemClassLoader().getResource(packName.replaceAll("[.]", "/"));

        if(url == null){
            return List.of();
        }

        File pack = new File(url.getPath());

        if(!pack.isDirectory() || pack.listFiles() == null){
            return List.of();
        }

        return Arrays.stream(pack.listFiles())
                .filter(File::isFile)
                .filter(f -> f.getName().endsWith(".class"))
                .map(f -> cl.findClass(packName + "." + f.getName().substring(0, f.getName().indexOf('.'))))
                .collect(Collectors.toList());
    }

    public static List<String> getSubpackagesRecursively(String packName){
        List<String> result = new LinkedList<>();

        for(String pack : getSubpackages(packName)){
            List<String> subPacks = getSubpackagesRecursively(pack);
            result.addAll(subPacks);
        }

        result.add(packName);

        return result.stream().distinct().collect(Collectors.toList());
    }

    public static List<String> getSubpackages(String packName){
        if(packName == null || packName.isEmpty()){
            return List.of();
        }

        URL url = ClassLoader.getSystemClassLoader().getResource(packName.replaceAll("[.]", "/"));

        if(url == null){
            return List.of();
        }

        File pack = new File(url.getPath());

        if(!pack.isDirectory() || pack.listFiles() == null){
            return List.of();
        }

        return Arrays.stream(pack.listFiles())
                .filter(File::isDirectory)
                .map(f -> packName + "." + f.getName())
                .collect(Collectors.toList());
    }

    private ReflectionUtils(){}

}

问题是在加载了传递的包中的所有 classes 之后,我试图过滤它们并只得到 @Component 的注释,但结果很奇怪:

someClass.isAnnotationPresent(Component.class) returns false,即使 someClass.getDeclaredAnnotations()

中有 @Component 注释

样本:

List<Class> componentClasses = new LinkedList<>();
        scans.forEach(s -> componentClasses.addAll(ReflectionUtils.getClassesInPackageRecursively(s)));
        System.out.printf("Classes: %d", componentClasses.size());
        componentClasses.forEach(c -> {
            System.out.println("-".repeat(50));
            System.out.println(Arrays.stream(c.getAnnotations()).map(Annotation::toString).collect(Collectors.joining(", ")));
            System.out.printf("%s -> %s\n", c.getName(), c.isAnnotationPresent(Component.class));
        });

输出:

...
@com.ltp.analog.core.annotation.Component()
com.ltp.analog.test.TestOne -> false
...

毫无疑问,问题是 classloader 恶作剧。

您有 2 个独立的 class 巧合地都命名为 com.ltp.analog.core.annotation.Component。尽管它们具有相同的名称,但它们不是一回事。

再来一次?是的,真的。

想象一下这段代码:

java.lang.String x = (java.lang.String) obj;

并且当您 运行 该代码时,您会收到此错误:

Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.String

你会:Whaaaa?我的 JVM 坏了!

但是没有,有一个解释。同样的事情也发生在你身上:有 2 个不同的 classes 但是它们都是相同的名字:它们都被命名com.ltp.analog.core.annotation.Component。当您调用 c.getAnnotations() 时,您会得到一个来自此 class 上的 'take' 的实例。它的 toString 仍然给你 com.ltp.analog.core.annotation.Component。当您调用 isAnnotationPresent 时,您的 Component.class 变量是另一个 'take'。因此,答案是否定的,同理.isAnnotationPresent(Override.class) returns false: 不一样class.

当你使用 classloader 并覆盖 loadClass 时,你会到达这里(如果你只覆盖 findClass,你应该永远无法进入这种情况)。

任何 class 实际上 由以下 两者 定义:

  • 它的完全限定名称
  • 它的装载机

它的加载器由 thatClassInstance.getClassLoader() returns 定义,它是 class 实际调用的加载器 .defineClass(byteArrayWithByteCodeInIt).

因此,您在一个加载器中加载了一次 Component,然后在另一个加载器中加载了一次;你用 c.getAnnotations() 得到的那个是由一个加载器加载的,而你粘贴的代码的 class 是由另一个加载器加载的。

解决方案更通用:覆盖loadClass 时需要非常非常小心。您不能使用任何类型 'across loaders' 除非该类型是由通用加载程序加载的。

因此,修复:

修复 AnalogClassLoader - 它需要正确地要求其父加载器加载 Component 而不是自己加载 .

您没有贴出 AnalogClassLOader 的代码,所以我无法提供更多关于如何完成此操作的提示。更一般地说,除非您完全理解它们,否则不要编写自己的 classloader,它们是非常棘手的野兽。