在運行時掃描 Java 註釋
一、簡介
眾所周知,在 Java 世界中,註解是獲取有關類和方法的元信息的一種非常有用的方式。
在本教程中,我們將討論在運行時掃描 Java 註釋。
2. 定義自定義註解
讓我們首先定義一個示例註釋和一個使用我們自定義註釋的示例類:
@Target({ METHOD, TYPE })
@Retention(RUNTIME)
public @interface SampleAnnotation {
String name();
}
@SampleAnnotation(name = "annotatedClass")
public class SampleAnnotatedClass {
@SampleAnnotation(name = "annotatedMethod")
public void annotatedMethod() {
//Do something
}
public void notAnnotatedMethod() {
//Do something
}
}
現在,我們已準備好在基於類和基於方法的用法上解析此自定義註釋的“名稱”屬性。
3. 使用 Java 反射掃描註解
在 Java Reflection
的幫助下,我們可以掃描特定的帶註釋的類或特定類的帶註釋的方法。
為了實現這個目標,我們需要使用ClassLoader
來加載類。因此,當我們知道要在哪個類中掃描註釋時,此方法很有用:
Class<?> clazz = ClassLoader.getSystemClassLoader()
.loadClass("com.baeldung.annotation.scanner.SampleAnnotatedClass");
SampleAnnotation classAnnotation = clazz.getAnnotation(SampleAnnotation.class);
Assert.assertEquals("SampleAnnotatedClass", classAnnotation.name());
Method[] methods = clazz.getMethods();
List<String> annotatedMethods = new ArrayList<>();
for (Method method : methods) {
SampleAnnotation annotation = method.getAnnotation(SampleAnnotation.class);
if (annotation != null) {
annotatedMethods.add(annotation.name());
}
}
Assert.assertEquals(1, annotatedMethods.size());
Assert.assertEquals("annotatedMethod", annotatedMethods.get(0));
4. 使用 Spring 上下文庫掃描註解
另一種掃描註釋的方法是使用 Spring Context 庫中包含的ClassPathScanningCandidateComponentProvider
類。
讓我們從添加[spring-context](https://search.maven.org/search?q=g:org.springframework%20a:spring-context)
依賴開始:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
讓我們繼續一個簡單的例子:
ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(SampleAnnotation.class));
Set<BeanDefinition> beanDefs = provider
.findCandidateComponents("com.baeldung.annotation.scanner");
List<String> annotatedBeans = new ArrayList<>();
for (BeanDefinition bd : beanDefs) {
if (bd instanceof AnnotatedBeanDefinition) {
Map<String, Object> annotAttributeMap = ((AnnotatedBeanDefinition) bd)
.getMetadata()
.getAnnotationAttributes(SampleAnnotation.class.getCanonicalName());
annotatedBeans.add(annotAttributeMap.get("name").toString());
}
}
Assert.assertEquals(1, annotatedBeans.size());
Assert.assertEquals("SampleAnnotatedClass", annotatedBeans.get(0));
與Java Reflections
完全不同的是,我們可以掃描所有的類而不需要知道具體的類名。
5. 使用 Spring Core Library 掃描註解
儘管 Spring Core 並沒有直接提供對我們代碼中所有註解的完整掃描,但我們仍然可以通過使用該庫的一些實用程序類來開發自己的完整註解掃描器。
首先,我們需要添加[spring-core](https://search.maven.org/search?q=g:org.springframework%20a:spring-core)
依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.22</version>
</dependency>
這裡有一個簡單的例子:
Class<?> userClass = ClassUtils.getUserClass(SampleAnnotatedClass.class);
List<String> annotatedMethods = Arrays.stream(userClass.getMethods())
.filter(method -> AnnotationUtils
.getAnnotation(method, SampleAnnotation.class) != null)
.map(method -> method.getAnnotation(SampleAnnotation.class)
.name())
.collect(Collectors.toList());
Assert.assertEquals(1, annotatedMethods.size());
Assert.assertEquals("annotatedMethod", annotatedMethods.get(0));
在AnnotationUtils
和ClassUtils
的幫助下,可以簡單地找到使用特定註解註解的方法和類。
6. 使用反射庫掃描註釋
Reflections
是一個據說是按照Scannotations
庫的精神編寫的庫。它掃描並索引項目的類路徑元數據。
讓我們添加[reflections](https://search.maven.org/search?q=g:org.reflections%20a:reflections)
依賴:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
現在我們準備好使用該庫來搜索帶註釋的類、方法、字段和類型:
Reflections reflections = new Reflections("com.baeldung.annotation.scanner");
Set<Method> methods = reflections
.getMethodsAnnotatedWith(SampleAnnotation.class);
List<String> annotatedMethods = methods.stream()
.map(method -> method.getAnnotation(SampleAnnotation.class)
.name())
.collect(Collectors.toList());
Assert.assertEquals(1, annotatedMethods.size());
Assert.assertEquals("annotatedMethod", annotatedMethods.get(0));
Set<Class<?>> types = reflections
.getTypesAnnotatedWith(SampleAnnotation.class);
List<String> annotatedClasses = types.stream()
.map(clazz -> clazz.getAnnotation(SampleAnnotation.class)
.name())
.collect(Collectors.toList());
Assert.assertEquals(1, annotatedClasses.size());
Assert.assertEquals("SampleAnnotatedClass", annotatedClasses.get(0));
正如我們所見, Reflections
庫提供了一種靈活的方式來掃描所有帶註釋的類和方法。所以我們不需要從SampleAnnotatedClass
開始。
7. 使用 Jandex 庫掃描註解
現在讓我們看看另一個名為Jandex
的庫,我們可以通過讀取我們代碼生成的Jandex
文件來在運行時掃描註釋。
這個庫引入了一個Maven
插件來生成一個包含與我們項目相關的元信息的Jandex
文件:
<plugin>
<groupId>org.jboss.jandex</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>1.2.3</version>
<executions>
<execution>
<phase>compile</phase>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
</fileSet>
</fileSets>
</configuration>
</execution>
</executions>
</plugin>
可以看到,運行maven-install
命令後,在META-INF
目錄下的classpath中生成了文件名為jandex.idx.
如果需要,我們也可以使用 Maven 的 rename 插件來修改文件名。
現在我們準備掃描任何類型的註釋。首先,我們需要添加[jandex](https://search.maven.org/search?q=g:org.jboss%20a:jandex)
依賴:
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<version>2.4.3.Final</version>
</dependency>
現在是時候掃描註釋了:
IndexReader reader = new IndexReader(appFile.getInputStream());
Index jandexFile = reader.read();
List<AnnotationInstance> appAnnotationList = jandexFile
.getAnnotations(DotName
.createSimple("com.baeldung.annotation.scanner.SampleAnnotation"));
List<String> annotatedMethods = new ArrayList<>();
List<String> annotatedClasses = new ArrayList<>();
for (AnnotationInstance annotationInstance : appAnnotationList) {
if (annotationInstance.target().kind() == AnnotationTarget.Kind.METHOD) {
annotatedMethods.add(annotationInstance.value("name")
.value()
.toString());
}
if (annotationInstance.target().kind() == AnnotationTarget.Kind.CLASS) {
annotatedClasses.add(annotationInstance.value("name")
.value()
.toString());
}
}
Assert.assertEquals(1, annotatedMethods.size());
Assert.assertEquals("annotatedMethod", annotatedMethods.get(0));
Assert.assertEquals(1, annotatedClasses.size());
Assert.assertEquals("SampleAnnotatedClass", annotatedClasses.get(0));
8. 結論
根據我們的要求;在運行時有多種掃描註解的方法。這些方法中的每一種都有自己的優點和缺點。我們可以決定考慮我們需要什麼。
這些示例的實現可在 GitHub 上獲得。