运行时扫描Java注解(二)之reflections解读
Reflections:运行时解析java元数据。 GitHub地址:https://github.com/ronmamo/reflections
Reflections基于反射和字节码,便于在运行时获取Java元数据,本文就它的使用和源码进行一定解读。
Reflections Api
关于如何使用,请参见官网,下面罗列一些提供的Api
//获取所有Reader的子类
Set<Class<? extends Reader>> subTypesOf = reflections.getSubTypesOf(Reader.class);
//获取所有使用了注解@Time的类
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Time.class);
//获取方法参数名称
List<String> names = reflections.getMethodParamNames(GitHubReader.class.getMethods()[0]);
//获取方法使用
Set<Member> usage = reflections.getMethodUsage(GitHubReader.class.getMethods()[0]);
// 获取资源
Set<String> properties = reflections.getResources(Pattern.compile(".*\\.properties"));
//获取使用了注解@Path的方法
Set<Method> resources = reflections.getMethodsAnnotatedWith(javax.ws.rs.Path.class);
注意:默认只会加载SubTypesScanner 和 TypeAnnotationsScanner两个扫描器,需要特别的Api,实例化reflections时可以设置Scanner。
VFS:a simple virtual file system bridge
正如在《运行时扫描Java注解(一)》中提到的,我们首先需要指定扫描目录。reflections对Jar、系统目录、Zip等进行了抽象,即VFS类,而ClasspathHelper则提供了获取扫描目录的一系列辅助方法:
public static Collection<URL> forPackage(String name, ClassLoader... classLoaders);
public static Collection<URL> forResource(String resourceName, ClassLoader... classLoaders);
public static URL forClass(Class<?> aClass, ClassLoader... classLoaders);
public static Collection<URL> forJavaClassPath();
public static Collection<URL> forWebInfLib(final ServletContext servletContext);
public static URL forWebInfClasses(final ServletContext servletContext);
...
扫描器接口Scanner
指定了扫描目录,就可以进行扫描。 reflections提供了若干扫描器,对类、方法、注解、资源、属性等进行扫描。
- SubTypesScanner 类型扫描
- TypeAnnotationsScanner 类型注解扫描
- MethodParameterScanner 方法参数扫描
- MethodParameterNamesScanner 方法参数名扫描
- MethodAnnotationsScanner 方法注解扫描
- MemberUsageScanner 方法使用扫描
- FieldAnnotationsScanner 属性注解扫描
- ResourcesScanner 资源扫描
- ...
这些所有的扫描器,都实现了接口Scanner
public interface Scanner {
void setConfiguration(Configuration configuration);
Multimap<String, String> getStore();
void setStore(Multimap<String, String> store);
Scanner filterResultsBy(Predicate<String> filter);
boolean acceptsInput(String file);
Object scan(Vfs.File file, @Nullable Object classObject);
boolean acceptResult(String fqn);
}
关于这些扫描器的具体代码实现,提供了Java反射和Javassist两种方式。
Java反射 VS Javassist
reflections默认是通过Javassist实现扫描器的,如果Javassist库不存在,则使用Java反射(注意:针对一些扫描器在这两种方式下,返回结果是不尽相同的)。 实现扫描器的抽象接口:
public interface MetadataAdapter<C,F,M> {
//
String getClassName(final C cls);
String getSuperclassName(final C cls);
List<String> getInterfacesNames(final C cls);
//
List<F> getFields(final C cls);
List<M> getMethods(final C cls);
String getMethodName(final M method);
List<String> getParameterNames(final M method);
List<String> getClassAnnotationNames(final C aClass);
List<String> getFieldAnnotationNames(final F field);
List<String> getMethodAnnotationNames(final M method);
List<String> getParameterAnnotationNames(final M method, final int parameterIndex);
String getReturnTypeName(final M method);
String getFieldName(final F field);
C getOfCreateClassObject(Vfs.File file) throws Exception;
String getMethodModifier(M method);
String getMethodKey(C cls, M method);
String getMethodFullKey(C cls, M method);
boolean isPublic(Object o);
boolean acceptsInput(String file);
}
实现的类名分别为JavassistAdapter和JavaReflectionAdapter。下面我们将以两个示例,对两者的实现进行比较。
- 获取注解的方法:getClassAnnotationNames、getFieldAnnotationNames、getMethodAnnotationNames、getParameterAnnotationNames等 Java反射只能获取到保留策略为运行时Runtime的注解,而无法获得Class策略的注解。而Javassist通过字节码操作这两种注解都可以获取到,比如获取Class注解:
(AnnotationsAttribute) classFile.getAttribute(AnnotationsAttribute.invisibleTag)
- 获取方法参数名扫描器:MethodParameterNamesScanner
众所周知,编译器在编译Java源码的时候,会把参数名编译成arg1等形式,不会保留原始参数名称。在Eclipse的编译选项中,可以勾选
Store information about method parameters(usable via reflection),从而通过Java反射获取参数信息。
在Javassist中,通过LocalVariableAttribute可以不需要勾选编译选项即可获得参数名称。以下代码截取自扫描器源码:
LocalVariableAttribute table = (LocalVariableAttribute) ((MethodInfo) method).getCodeAttribute().getAttribute(LocalVariableAttribute.tag);
int length = table.tableLength();
int i = Modifier.isStatic(((MethodInfo) method).getAccessFlags()) ? 0 : 1; //skip this
if (i < length) {
List<String> names = new ArrayList<String>(length - i);
while (i < length) names.add(((MethodInfo) method).getConstPool().getUtf8Info(table.nameIndex(i++)));
getStore().put(key, Joiner.on(", ").join(names));
}
更多More
关于Java反射和Javassist,将在运行时扫描Java注解(三)和运行时扫描Java注解(四)作更进一步的探讨。