sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

运行时扫描Java注解(二)之reflections解读

Open Sayi opened this issue 8 years ago • 0 comments

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);

注意:默认只会加载SubTypesScannerTypeAnnotationsScanner两个扫描器,需要特别的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提供了若干扫描器,对类、方法、注解、资源、属性等进行扫描。

  1. SubTypesScanner 类型扫描
  2. TypeAnnotationsScanner 类型注解扫描
  3. MethodParameterScanner 方法参数扫描
  4. MethodParameterNamesScanner 方法参数名扫描
  5. MethodAnnotationsScanner 方法注解扫描
  6. MemberUsageScanner 方法使用扫描
  7. FieldAnnotationsScanner 属性注解扫描
  8. ResourcesScanner 资源扫描
  9. ...

这些所有的扫描器,都实现了接口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);
    
}

实现的类名分别为JavassistAdapterJavaReflectionAdapter。下面我们将以两个示例,对两者的实现进行比较。

  1. 获取注解的方法:getClassAnnotationNames、getFieldAnnotationNames、getMethodAnnotationNames、getParameterAnnotationNames等 Java反射只能获取到保留策略为运行时Runtime的注解,而无法获得Class策略的注解。而Javassist通过字节码操作这两种注解都可以获取到,比如获取Class注解:
(AnnotationsAttribute) classFile.getAttribute(AnnotationsAttribute.invisibleTag)
  1. 获取方法参数名扫描器: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注解(四)作更进一步的探讨。

Sayi avatar Sep 21 '17 04:09 Sayi