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,它们是非常棘手的野兽。
我有自己的注释:
@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,它们是非常棘手的野兽。