封面来源:本文封面来源于网络,如有侵权,请联系删除。

Reflections 的 GitHub 仓库:Reflections GitHub

PS:本文基于 Reflections 0.9.12 进行编写,高版本 API 的使用与本文存在差异。

1. Reflections 的基本介绍

以下内容翻译自官方 GitHub 仓库介绍:

Reflections 通过扫描类路径(ClassPath),为元数据建立索引,使您可以在运行时对其进行查询,并可以保存和收集项目中许多模块的信息。

使用 Reflections 可以查询以下元数据信息:

  1. 获取某个类的所有子类
  2. 获取被某个注解标记的所有类型/成员变量,支持注解参数匹配
  3. 使用正则表达式获得所有匹配的资源文件
  4. 获取具有特定签名(包括参数,参数注释和返回类型)的所有方法

2. Reflections 的简单使用

2.1 项目与数据的准备

依赖的导入

为便于测试,在此导入 JUnit5 与 AssertJ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.12</version>
</dependency>

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
</dependency>

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>

数据准备

首先自定义以下五个注解,它们作用于不同的位置,便于后续测试 Reflections。

它们都位于项目的 indi.mofan.reflections.annotaion 包下。

作用于构造方法上的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package indi.mofan.reflections.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author mofan
 * @date 2021/3/22 9:53
 */
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationForConstructor {
}

作用于字段上的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package indi.mofan.reflections.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * @author mofan
 * @date 2021/3/22 9:56
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) // 注意此处是 RUNTIME
public @interface AnnotationForField {
}

作用于方法上的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package indi.mofan.reflections.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author mofan
 * @date 2021/3/22 9:51
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationForMethod {
}

作用于方法参数上的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package indi.mofan.reflections.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author mofan
 * @date 2021/3/23 13:42
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationForParameter {
}

作用于类上的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package indi.mofan.reflections.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author mofan
 * @date 2021/3/22 9:30
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationForType {
}

再创建一个抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package indi.mofan.reflections.entity;

import java.io.Serializable;
import java.util.Date;

/**
 * @author mofan
 * @date 2021/3/20
 */
public abstract class BaseEntity implements Serializable {
    private static final long serialVersionUID = 5068983365017772168L;

    private Long id;
    private Date createTime;
    private Date updateTime;
}

最后创建这个抽象类的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package indi.mofan.reflections.entity;

import indi.mofan.reflections.annotaion.AnnotationForConstructor;
import indi.mofan.reflections.annotaion.AnnotationForField;
import indi.mofan.reflections.annotaion.AnnotationForMethod;
import indi.mofan.reflections.annotaion.AnnotationForParameter;
import indi.mofan.reflections.annotaion.AnnotationForType;

/**
 * @author mofan
 * @date 2021/3/20
 */
@AnnotationForType
public class UserInfo extends BaseEntity {
    private static final long serialVersionUID = -3912662513111527890L;

    @AnnotationForField
    private String username;
    private String pwd;
    private String gender;

    @AnnotationForConstructor
    public UserInfo() {
    }

    public UserInfo(String username) {
        this.username = username;
    }

    public UserInfo(String username, String pwd) {
        this(username);
        this.pwd = pwd;
    }

    public UserInfo(String username, 
                    @AnnotationForParameter String pwd, 
                    String gender) {
        this.username = username;
        this.pwd = pwd;
        this.gender = gender;
    }

    @AnnotationForMethod
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(@AnnotationForParameter String gender) {
        this.gender = gender;
    }

    public String useMethod() {
        return getUsername() + getGender();
    }
}

抽象类及其子类位于 indi.mofan.reflections.entity 包下。

项目目录结构

Reflections学习项目目录结构

相关说明

如果未配置任何扫描器(xxxScanner),则将使用默认配置的 SubTypesScannerTypeAnnotationsScanner

还可以配置类加载器(Classloader),该类加载器将用于从名称解析运行时类。

Reflections 默认会扩展超类,这解决了可传递的 URL 不被扫描的一些问题。

2.2 与注解相关的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
 * @author mofan
 * @date 2021/3/20
 */
public class ReflectionsTest implements WithAssertions {

    private static String basePackageName = "indi.mofan";
    private static String packageName = "indi.mofan.reflections";

    @Test
    @SneakyThrows
    public void testAnnotation() {
        // 配置扫描的包 ClasspathHelper.forPackage 有坑,不要用!! https://github.com/ronmamo/reflections/issues/178
        //        Collection<URL> forPackage = ClasspathHelper.forPackage(packageName);
        // 需设置为 false,否则 getAllTypes() 方法将报错
        SubTypesScanner subTypesScanner = new SubTypesScanner(false);
        // 配置注解扫描器
        TypeAnnotationsScanner typeAnnotationsScanner = new TypeAnnotationsScanner();
        MethodAnnotationsScanner methodAnnotationsScanner = new MethodAnnotationsScanner();
        FieldAnnotationsScanner fieldAnnotationsScanner = new FieldAnnotationsScanner();
        MethodParameterScanner methodParameterScanner = new MethodParameterScanner();
        Scanner[] scanners = {subTypesScanner, typeAnnotationsScanner, methodAnnotationsScanner,
                              fieldAnnotationsScanner, methodParameterScanner};
        // 使用配置
        Reflections reflections = new Reflections(packageName, scanners);


        // 获取某个包下某个类的子类
        Set<Class<? extends BaseEntity>> subTypesOf =
            reflections.getSubTypesOf(BaseEntity.class);
        assertThat(subTypesOf).containsOnly(UserInfo.class);
        // 获取所有 Object 类的子类,不推荐使用
        Set<String> allTypes = reflections.getAllTypes();
        List<String> resultTypes = Arrays.asList(
            AnnotationForField.class.getName(),
            AnnotationForParameter.class.getName(),
            AnnotationForConstructor.class.getName(),
            AnnotationForMethod.class.getName(),
            AnnotationForType.class.getName(),
            UserInfo.class.getName(),
            BaseEntity.class.getName()
        );
        assertThat(allTypes).containsAll(resultTypes);
        // 获取某个包下被某个注解注释的类
        Class<UserInfo> userInfoClass = UserInfo.class;
        Set<Class<?>> typesAnnotatedWith =
            reflections.getTypesAnnotatedWith(AnnotationForType.class, true);
        assertThat(typesAnnotatedWith).containsOnly(UserInfo.class);
        // 获取某个包下被某个注解注释的方法
        Set<Method> methodsAnnotatedWith =
            reflections.getMethodsAnnotatedWith(AnnotationForMethod.class);
        Method getUsername = userInfoClass.getMethod("getUsername");
        assertThat(methodsAnnotatedWith).containsOnly(getUsername);
        // 获取某个包下被某个注解注释的构造方法
        Set<Constructor> constructorsAnnotatedWith =
            reflections.getConstructorsAnnotatedWith(AnnotationForConstructor.class);
        Constructor<UserInfo> constructor = userInfoClass.getConstructor();
        assertThat(constructorsAnnotatedWith).containsOnly(constructor);
        // 获取某个包下被某个注解注释的字段
        Set<Field> fieldsAnnotatedWith =
            reflections.getFieldsAnnotatedWith(AnnotationForField.class);
        Field usernameField = userInfoClass.getDeclaredField("username");
        assertThat(fieldsAnnotatedWith).containsOnly(usernameField);
    }
}

Reflections 提供了两种方式来设置扫描的包路径:

  1. 直接使用构造方法传入;
  2. 使用 ConfigurationBuilderClasspathHelper.forPackage() 直接或间接地传入。

Issue #178 未关闭前,ClasspathHelper.forPackage() 返回的并不是用户传入的路径,而是传入路径的上一级路径,应当放弃使用这种方式,而是直接使用构造方法传入要扫描的包路径。

2.3 与方法相关的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
@SneakyThrows
public void testMethod() {
    MethodParameterNamesScanner methodParameterNamesScanner = new MethodParameterNamesScanner();
    MethodParameterScanner methodParameterScanner = new MethodParameterScanner();
    MemberUsageScanner memberUsageScanner = new MemberUsageScanner();
    Scanner[] scanners = {methodParameterNamesScanner, methodParameterScanner, memberUsageScanner};
    Reflections reflections = new Reflections(packageName, scanners);

    // 获取方法参数名
    Method setUsername = UserInfo.class.getMethod("setUsername", String.class);
    List<String> methodParamNames = reflections.getMethodParamNames(setUsername);
    assertThat(methodParamNames).containsOnly("username");
    // 获取指定参数类型的方法 ---> 如果不写,就是获取无参方法
    Set<Method> methodsMatchParams = reflections.getMethodsMatchParams(String.class);
    Class<UserInfo> userInfoClass = UserInfo.class;
    Method setGender = userInfoClass.getMethod("setGender", String.class);
    Method setPwd = userInfoClass.getMethod("setPwd", String.class);
    assertThat(methodsMatchParams).containsExactlyInAnyOrder(setUsername, setGender, setPwd);
    // 获取指定返回值类型的方法
    Set<Method> methodsReturn = reflections.getMethodsReturn(String.class);
    Method useMethod = userInfoClass.getMethod("useMethod");
    Method getGender = userInfoClass.getMethod("getGender");
    Method getUsername = userInfoClass.getMethod("getUsername");
    Method getPwd = userInfoClass.getMethod("getPwd");
    assertThat(methodsReturn).containsExactlyInAnyOrder(useMethod, getGender, getUsername, getPwd);
    // 获取任何参数上带有指定注解的方法
    Set<Method> methodsWithAnyParamAnnotated =
            reflections.getMethodsWithAnyParamAnnotated(AnnotationForParameter.class);
    assertThat(methodsWithAnyParamAnnotated).containsOnly(setGender);
    // 获取某个方法的被哪些方法使用了,不推荐使用
    Set<Member> methodUsage =
            reflections.getMethodUsage(UserInfo.class.getMethod("getUsername"));
    assertThat(methodUsage).containsOnly(useMethod);
}

2.4 与构造方法相关的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
@SneakyThrows
public void testConstructor() {
    MethodParameterNamesScanner methodParameterNamesScanner = new MethodParameterNamesScanner();
    MethodParameterScanner methodParameterScanner = new MethodParameterScanner();
    MemberUsageScanner memberUsageScanner = new MemberUsageScanner();
    Scanner[] scanners = {methodParameterNamesScanner, methodParameterScanner, memberUsageScanner};
    Reflections reflections = new Reflections(packageName, scanners);
    // 获取指定参数类型的构造方法参数名
    Constructor<UserInfo> constructor = UserInfo.class.getConstructor(String.class);
    List<String> constructorParamNames = reflections.getConstructorParamNames(constructor);
    assertThat(constructorParamNames).containsOnly("username");
    // 获取指定参数类型的构造方法
    Set<Constructor> constructorsMatchParams = reflections.getConstructorsMatchParams(String.class);
    assertThat(constructorsMatchParams).containsOnly(constructor);
    // 获取任何参数上带有指定注解的构造方法
    Set<Constructor> constructorsWithAnyParamAnnotated =
            reflections.getConstructorsWithAnyParamAnnotated(AnnotationForParameter.class);
    assertThat(constructorsWithAnyParamAnnotated).containsOnly(
            UserInfo.class.getConstructor(String.class, String.class, String.class)
    );
    // 获取某个构造方法的使用情况
    Set<Member> constructorUsage = reflections.getConstructorUsage(constructor);
    assertThat(constructorUsage).containsOnly(
            UserInfo.class.getConstructor(String.class, String.class)
    );
}

2.5 与资源文件相关的 API

在 resources 目录下创建 my.properties 文件。

1
2
3
4
5
6
7
8
@Test
public void testResources() {
    Reflections reflections = new Reflections(basePackageName, new ResourcesScanner());
    // 获取资源文件的相对路径,使用正则表达式进行匹配
    Set<String> properties =
            reflections.getResources(Pattern.compile(".*\\.properties"));
    assertThat(properties).contains("indi/mofan/my.properties");
}

2.6 ReflectionUtils 的简单使用

ReflectionsUtils 包含一些便捷的 Java 反射帮助器方法,用于获取与某些 predicates 匹配的类型 / 构造函数 / 方法 / 字段 / 注释,通常采用 ReflectionUtils.getAllXxx(type,withYYY)的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Test
@SneakyThrows
@SuppressWarnings({"unchecked", "varargs"})
public void testReflectionUtils() {
    // 必须是 public 方法
    Predicate<Method> publicPredicate = ReflectionUtils.withModifier(Modifier.PUBLIC);
    // 有 get 前缀
    Predicate<Method> getPredicate = ReflectionUtils.withPrefix("get");
    // 参数个数为 0
    Predicate<Member> paramPredicate = ReflectionUtils.withParametersCount(0);
    Class<UserInfo> userInfoClass = UserInfo.class;
    Set<Method> methods = ReflectionUtils.getAllMethods(userInfoClass, publicPredicate, getPredicate, paramPredicate);
    assertThat(methods).contains(
            userInfoClass.getMethod("getGender"),
            userInfoClass.getMethod("getUsername"),
            userInfoClass.getMethod("getPwd")
    );

    // 参数必须是 Collection 及其子类
    Predicate<Member> paramsPredicate = ReflectionUtils.withParametersAssignableTo(Collection.class);
    // 返回类型是 boolean
    Predicate<Method> returnPredicate = ReflectionUtils.withReturnType(boolean.class);
    methods = ReflectionUtils.getAllMethods(LinkedList.class, paramsPredicate, returnPredicate);
    assertThat(methods).isNotEmpty()
            .allMatch(i -> i.getName().endsWith("All"))
            .hasSize(13);

    // 字段有注解 AnnotationForField(注解的 RetentionPolicy 必须是 RUNTIME!)
    Predicate<Field> annotationPredicate = ReflectionUtils.withAnnotation(AnnotationForField.class);
    // 字段类型是 CharSequence 及其子类
    Predicate<Field> typeAssignablePredicate = ReflectionUtils.withTypeAssignableTo(CharSequence.class);
    Set<Field> fields = ReflectionUtils.getAllFields(UserInfo.class, annotationPredicate, typeAssignablePredicate);
    assertThat(fields).isNotEmpty().map(Field::getName).containsOnly("username");
}

注意: 在最后一项测试中想要获取被某个注解标记的字段时,该注解上的元注解 @Retention 的属性值必须是 RetentionPolicy.RUNTIME,否则在运行时无法正确获取到目标字段。