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

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,否则在运行时无法正确获取到目标字段。