封面来源:本文封面来源于网络,如有侵权,请联系删除。
Reflections 的 GitHub 仓库:Reflections GitHub
PS:本文基于 Reflections 0.9.12 进行编写,高版本 API 的使用与本文存在差异。
1. Reflections 的基本介绍
以下内容翻译自官方 GitHub 仓库介绍:
Reflections 通过扫描类路径(ClassPath),为元数据建立索引,使您可以在运行时对其进行查询,并可以保存和收集项目中许多模块的信息。
使用 Reflections 可以查询以下元数据信息:
- 获取某个类的所有子类
- 获取被某个注解标记的所有类型/成员变量,支持注解参数匹配
- 使用正则表达式获得所有匹配的资源文件
- 获取具有特定签名(包括参数,参数注释和返回类型)的所有方法
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;
@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;
@Target(ElementType.FIELD) @Retention(RetentionPolicy.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;
@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;
@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;
@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;
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;
@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
包下。
项目目录结构
相关说明
如果未配置任何扫描器(xxxScanner
),则将使用默认配置的 SubTypesScanner
和 TypeAnnotationsScanner
。
还可以配置类加载器(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
|
public class ReflectionsTest implements WithAssertions {
private static String basePackageName = "indi.mofan"; private static String packageName = "indi.mofan.reflections";
@Test @SneakyThrows public void testAnnotation() { 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); 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
提供了两种方式来设置扫描的包路径:
- 直接使用构造方法传入;
- 使用
ConfigurationBuilder
与 ClasspathHelper.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() { Predicate<Method> publicPredicate = ReflectionUtils.withModifier(Modifier.PUBLIC); Predicate<Method> getPredicate = ReflectionUtils.withPrefix("get"); 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") );
Predicate<Member> paramsPredicate = ReflectionUtils.withParametersAssignableTo(Collection.class); 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);
Predicate<Field> annotationPredicate = ReflectionUtils.withAnnotation(AnnotationForField.class); 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
,否则在运行时无法正确获取到目标字段。