在构建时将注解添加到 Java getter, getX(), 当字段 x 被注解时
Adding annotations at build time to a Java getter, getX(), when the field x is annotated
我想创建以下 Java 注释,并在构建时处理它:
@Target(value = FIELD)
interface @AnnotateGetter {
Annotation[] value();
}
如果一个字段field
被注释为@AnnotateGetter
,那么value
数组中的所有Annotations
都被添加到getField()
的方法中如果存在这样的方法,则相同 class。
最简单的方法是什么?
- ApectJ,它可以使用声明注释语句向方法添加注释,但是,虽然我知道如何 select 用
@AnnotateGetter
注释的字段,但我不知道如何 select 一个方法对应于用 @AnnotateGetter
注释的字段
- 其他一些AOP框架
- 编写我自己的
javax.annotation.processing.Processor
调用一些可以向方法添加注释的库。这种图书馆的最佳选择是什么?它是否必须在 javac 编译源文件之后操作字节码,或者我是否可以在生成 class 文件之前以某种方式连接到 javac 并在编译期间添加注释?
- 其他...
您可以试试我的图书馆 Byte Buddy。您可以在应用程序启动时的构建过程中 运行 它,甚至可以从 Java 代理。使用最新版本可以按如下方式创建注释:
DynamicType.Unloaded<?> type = new ByteBuddy()
.makeAnnotation()
.name("AnnotateGetter")
.annotateType(new Target() {
public ElementType value() { return ElementType.FIELD; }
public Class<? extends Annotation> annotationType() { return Target.class; }
}).defineMethod("value",
SomeAnnotation.class,
Collections.emptyList(),
Visibility.PUBLIC)
.withoutCode()
.make();
然后您可以通过添加此生成注释的实例来创建或操作现有的 类。特定领域的语言保持相似。请查阅 detailed introduction 库的教程。
这是一个通过 AspectJ 使用 APT(注释处理工具)的解决方案。它将指定的注释添加到 getter 方法,但不会从字段中删除它们。所以这是一个 "copy" 动作,而不是 "move".
注释处理支持已添加到 AspectJ 版本 1.8.2 中,并在 release notes. Here is some self-consistent sample code. I compiled it from the command line because from Eclipse I failed to get it running according to AspectJ maintainer Andy Clement's description.
中进行了描述
好吧,假设我们有一个(Eclipse 或其他)项目目录,目录布局如下:
SO_AJ_APT_MoveAnnotationsFromMemberToGetter
compile_run.bat
src
de/scrum_master/app/Person.java
src_apt
de/scrum_master/app/AnnotatedGetterProcessor.java
de/scrum_master/app/AnnotateGetter.java
de/scrum_master/app/CollationType.java
de/scrum_master/app/SortOrder.java
de/scrum_master/app/Unique.java
META-INF/services/javax.annotation.processing.Processor
src和src_apt都是源目录,compile_run.bat 是一个 Windows 分两个阶段构建项目的批处理文件(首先是注释处理器,然后是项目的其余部分)和 运行 最终结果以证明它确实做了它应该做的.
用于字段的注释,稍后复制到 getter 方法:
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface Unique {}
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface SortOrder {
String value() default "ascending";
}
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface CollationType {
String value() default "alphabetical";
String language() default "EN";
}
元注释指定要复制到 getter 方法的字段注释:
请注意,此元注释仅用于注释处理,因此具有 SOURCE
保留范围。
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AnnotateGetter {
Class<? extends Annotation>[] value();
}
驱动申请:
注意事项:
有四个带注解的字段(id,firstName,lastName, fieldWithoutGetter), 但只有前三个有对应的 getter 方法,最后一个没有。因此,我们希望 fieldWithoutGetter 稍后能够妥善处理,稍后通过 APT 生成空的或没有 ITD 方面。
class Person
上的元注释 @AnnotateGetter({ Unique.class, SortOrder.class, CollationType.class })
指定要考虑将哪些注释复制到 getter 方法。稍后您可以尝试一下,看看如果删除其中任何一个,结果会如何变化。
我们还有一些虚拟方法 doSomething()
和 doSomethingElse()
,它们应该不受以后任何注释复制的影响,即它们不应该通过 AspectJ 获得任何新注释。 (有一个否定的测试用例总是好的。)
main(..)
方法使用反射来打印所有字段和方法,包括它们的注释。
package de.scrum_master.app;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@AnnotateGetter({ Unique.class, SortOrder.class, CollationType.class })
public class Person {
@Unique
private final int id;
@SortOrder("descending")
@CollationType("alphabetical")
private final String firstName;
@SortOrder("random")
@CollationType(value = "alphanumeric", language = "DE")
private final String lastName;
@SortOrder("ascending")
@CollationType(value = "numeric")
private final int fieldWithoutGetter;
public Person(int id, String firstName, String lastName, int fieldWithoutGetter) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.fieldWithoutGetter = fieldWithoutGetter;
}
public int getId() { return id; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public void doSomething() {}
public void doSomethingElse() {}
public static void main(String[] args) {
System.out.println("Field annotations:");
for (Field field : Person.class.getDeclaredFields()) {
System.out.println(" " + field.getName());
for (Annotation annotation : field.getAnnotations())
System.out.println(" " + annotation);
}
System.out.println();
System.out.println("Method annotations:");
for (Method method : Person.class.getDeclaredMethods()) {
System.out.println(" " + method.getName());
for (Annotation annotation : method.getAnnotations())
System.out.println(" " + annotation);
}
}
}
没有 APT + AspectJ 的控制台输出:
如您所见,打印了字段注释,但没有打印方法注释,因为我们尚未定义注释处理器(见下文)。
Field annotations:
id
@de.scrum_master.app.Unique()
firstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
lastName
@de.scrum_master.app.SortOrder(value=random)
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
fieldWithoutGetter
@de.scrum_master.app.SortOrder(value=ascending)
@de.scrum_master.app.CollationType(value=numeric, language=EN)
Method annotations:
main
getId
doSomething
doSomethingElse
getFirstName
getLastName
注释处理器:
现在我们需要一个注释处理器,为每个要复制的字段和注释组合生成一个方面。这样的一个方面应该是这样的:
package de.scrum_master.app;
public aspect AnnotateGetterAspect_Person_CollationType_lastName {
declare @method : * Person.getLastName() : @de.scrum_master.app.CollationType(value = "alphanumeric", language = "DE");
}
很简单,不是吗?注释处理器应将这些方面生成到目录 .apt_generated 中。 AspectJ 编译器会为我们处理这些,我们稍后会看到。但首先是注解处理器(抱歉代码太长,但这正是您所要求的):
package de.scrum_master.app;
import java.io.*;
import java.util.*;
import javax.tools.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotatedGetterProcessor extends AbstractProcessor {
private Filer filer;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
}
@SuppressWarnings("unchecked")
@Override
public boolean process(
Set<? extends TypeElement> elements,
RoundEnvironment env
) {
// Get classes annotated with something like: @AnnotateGetter({ Foo.class, Bar.class, Zot.class })
env.getElementsAnnotatedWith(AnnotateGetter.class)
.stream()
.filter(annotatedClass -> annotatedClass.getKind() == ElementKind.CLASS)
// For each filtered class, copy designated field annotations to corresponding getter method, if present
.forEach(annotatedClass -> {
String packageName = annotatedClass.getEnclosingElement().toString().substring(8);
String className = annotatedClass.getSimpleName().toString();
/*
* Unfortunately when we do something like this:
* AnnotateGetter annotateGetter = annotatedClass.getAnnotation(AnnotateGetter.class);
* Class<? extends Annotation> annotationToBeConverted = annotateGetter.value()[0];
* We will get this exception:
* Internal compiler error:
* javax.lang.model.type.MirroredTypesException:
* Attempt to access Class objects for TypeMirrors
* [de.scrum_master.app.Unique, de.scrum_master.app.SortOrder, de.scrum_master.app.CollationType]
* at org.aspectj.org.eclipse.jdt.internal.compiler.apt.model.AnnotationMirrorImpl.getReflectionValue
*
* Thus, we have to use annotation mirrors instead of annotation classes directly,
* then tediously extracting annotation values from a nested data structure. :-(
*/
// Find @AnnotateGetter annotation and extract its array of values from deep within
((List<? extends AnnotationValue>) annotatedClass.getAnnotationMirrors()
.stream()
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(AnnotateGetter.class.getName()))
.map(AnnotationMirror::getElementValues)
.map(Map::values)
.findFirst()
.get()
.stream()
.map(AnnotationValue::getValue)
.findFirst()
.get()
)
.stream()
.map(annotationValueToBeCopied -> (TypeElement) ((DeclaredType) annotationValueToBeCopied.getValue()).asElement())
// For each annotation to be copied, get all correspondingly annotated fields
.forEach(annotationTypeElementToBeCopied -> {
env.getElementsAnnotatedWith(annotationTypeElementToBeCopied)
.stream()
.filter(annotatedField -> ((Element) annotatedField).getKind() == ElementKind.FIELD)
// For each annotated field create an ITD aspect
.forEach(annotatedField -> {
String fieldName = annotatedField.getSimpleName().toString();
String aspectName =
"AnnotateGetterAspect_" + className + "_" +
annotationTypeElementToBeCopied.getSimpleName() + "_" + fieldName;
StringBuilder annotationDeclaration = new StringBuilder()
.append("@" + annotationTypeElementToBeCopied.getQualifiedName() + "(");
annotatedField.getAnnotationMirrors()
.stream()
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(annotationTypeElementToBeCopied.getQualifiedName().toString()))
.map(AnnotationMirror::getElementValues)
.forEach(annotationParameters -> {
annotationParameters.entrySet()
.stream()
.forEach(annotationParameter -> {
ExecutableElement annotationParameterType = annotationParameter.getKey();
AnnotationValue annotationParameterValue = annotationParameter.getValue();
annotationDeclaration.append(annotationParameterType.getSimpleName() + " = ");
if (annotationParameterType.getReturnType().toString().equals("java.lang.String"))
annotationDeclaration.append("\"" + annotationParameterValue + "\"");
else
annotationDeclaration.append(annotationParameterValue);
annotationDeclaration.append(", ");
});
if (!annotationParameters.entrySet().isEmpty())
annotationDeclaration.setLength(annotationDeclaration.length() - 2);
annotationDeclaration.append(")");
});
// For each field with the current annotation, create an ITD aspect
// adding the same annotation to the member's getter method
String aspectSource = createAspectSource(
annotatedClass, packageName, className,
annotationDeclaration.toString(), fieldName, aspectName
);
writeAspectSourceToDisk(packageName, aspectName, aspectSource);
});
});
});
return true;
}
private String createAspectSource(
Element parentElement,
String packageName,
String className,
String annotationDeclaration,
String fieldName,
String aspectName
) {
String getterMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
StringBuilder aspectSource = new StringBuilder()
.append("package " + packageName + ";\n\n")
.append("public aspect " + aspectName + " {\n");
for (Element childElement : parentElement.getEnclosedElements()) {
// Search for methods
if (childElement.getKind() != ElementKind.METHOD)
continue;
ExecutableElement method = (ExecutableElement) childElement;
// Search for correct getter method name
if (!method.getSimpleName().toString().equals(getterMethodName))
continue;
// Parameter list for a getter method must be empty
if (!method.getParameters().isEmpty())
continue;
// Getter method must be public
if (!method.getModifiers().contains(Modifier.PUBLIC))
continue;
// Getter method must be non-static
if (method.getModifiers().contains(Modifier.STATIC))
continue;
// Add call to found method
aspectSource.append(
" declare @method : * " + className + "." + getterMethodName + "() : " +
annotationDeclaration + ";\n"
);
}
aspectSource.append("}\n");
return aspectSource.toString();
}
private void writeAspectSourceToDisk(
String packageName,
String aspectName,
String aspectSource
) {
try {
JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName);
file.openWriter().append(aspectSource).close();
System.out.println("Generated aspect " + packageName + "." + aspectName);
} catch (IOException ioe) {
// Message "already created" can appear if processor runs more than once
if (!ioe.getMessage().contains("already created"))
ioe.printStackTrace();
}
}
}
注解处理器我就不多说了,请仔细阅读。我还添加了一些源代码注释,希望它们足以被理解。
src_apt/META-INF/services/javax.annotation.processing.Processor:
我们需要此文件供注释处理器稍后与 AspectJ 编译器 (ajc) 结合使用。
de.scrum_master.app.AnnotatedGetterProcessor
批处理文件构建和运行宁项目:
抱歉,如果这是特定于平台的,但我想您可以轻松地将其转换为 UNIX/Linux shell 脚本,非常简单。
@echo off
set SRC_PATH=C:\Users\Alexander\Documents\java-src
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ
echo Building annotation processor
cd "%SRC_PATH%\SO_AJ_APT_MoveAnnotationsFromMemberToGetter"
rmdir /s /q bin
del /q processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src_apt -d bin -cp "%ASPECTJ_HOME%\lib\aspectjrt.jar"
jar -cvf processor.jar -C src_apt META-INF -C bin .
echo.
echo Generating aspects and building project
rmdir /s /q bin .apt_generated
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src -d bin -s .apt_generated -inpath processor.jar -cp "%ASPECTJ_HOME%\lib\aspectjrt.jar";processor.jar -showWeaveInfo
echo.
echo Running de.scrum_master.app.Person
java -cp bin;"%ASPECTJ_HOME%\lib\aspectjrt.jar" de.scrum_master.app.Person
构建控制台日志 + 运行 进程:
构建处理器+注释classes,然后将它们打包成processor.jar:
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 45) (aus = 46)(-2 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/AnnotatedGetterProcessor.class wird hinzugefügt(ein = 8065) (aus = 3495)(56 % verkleinert)
de/scrum_master/app/AnnotateGetter.class wird hinzugefügt(ein = 508) (aus = 287)(43 % verkleinert)
de/scrum_master/app/CollationType.class wird hinzugefügt(ein = 520) (aus = 316)(39 % verkleinert)
de/scrum_master/app/SortOrder.class wird hinzugefügt(ein = 476) (aus = 296)(37 % verkleinert)
de/scrum_master/app/Unique.class wird hinzugefügt(ein = 398) (aus = 248)(37 % verkleinert)
Aspect 生成 + 项目构建(由于其内置的注释处理支持,仅需一次 AspectJ 编译器调用即可完成):
Generating aspects and building project
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_Unique_id
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_fieldWithoutGetter
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_firstName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_lastName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_fieldWithoutGetter
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_firstName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_lastName
'public int de.scrum_master.app.Person.getId()' (Person.java:31) is annotated with @de.scrum_master.app.Unique method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_Unique_id' (AnnotateGetterAspect_Person_Unique_id.java:4)
'public java.lang.String de.scrum_master.app.Person.getFirstName()' (Person.java:32) is annotated with @de.scrum_master.app.SortOrder method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_firstName' (AnnotateGetterAspect_Person_SortOrder_firstName.java:4)
'public java.lang.String de.scrum_master.app.Person.getFirstName()' (Person.java:32) is annotated with @de.scrum_master.app.CollationType method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_firstName' (AnnotateGetterAspect_Person_CollationType_firstName.java:4)
'public java.lang.String de.scrum_master.app.Person.getLastName()' (Person.java:33) is annotated with @de.scrum_master.app.CollationType method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_lastName' (AnnotateGetterAspect_Person_CollationType_lastName.java:4)
'public java.lang.String de.scrum_master.app.Person.getLastName()' (Person.java:33) is annotated with @de.scrum_master.app.SortOrder method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_lastName' (AnnotateGetterAspect_Person_SortOrder_lastName.java:4)
最后但同样重要的是,我们再次 运行 驱动程序应用程序。这次我们应该看到从带注释的字段复制到相应的 getter 方法(如果存在这样的方法)的注释:
Running de.scrum_master.app.Person
Field annotations:
id
@de.scrum_master.app.Unique()
firstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
lastName
@de.scrum_master.app.SortOrder(value=random)
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
fieldWithoutGetter
@de.scrum_master.app.SortOrder(value=ascending)
@de.scrum_master.app.CollationType(value=numeric, language=EN)
Method annotations:
main
getId
@de.scrum_master.app.Unique()
doSomethingElse
getLastName
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
@de.scrum_master.app.SortOrder(value=random)
getFirstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
doSomething
瞧!享受并随时提问。 :-)
更新(2015-05-03):注意,在我的注解处理器代码中,我最初忘记也复制注解参数值,因此只创建了默认值每个注释。我刚刚修复了这个问题,使注释处理器代码变得更加冗长。因为我想让它值得重构代码并从中学习一些东西,即使你已经接受了原始答案并以另一种方式解决了你的问题,我还是玩了 Java 8 种东西,比如 lambdas,流,过滤器,地图。如果这个概念对您来说是新的,特别是对于嵌套的 forEach
循环,这不是特别可读,但我想尝试看看我能用它走多远。 ;-)
我想创建以下 Java 注释,并在构建时处理它:
@Target(value = FIELD)
interface @AnnotateGetter {
Annotation[] value();
}
如果一个字段field
被注释为@AnnotateGetter
,那么value
数组中的所有Annotations
都被添加到getField()
的方法中如果存在这样的方法,则相同 class。
最简单的方法是什么?
- ApectJ,它可以使用声明注释语句向方法添加注释,但是,虽然我知道如何 select 用
@AnnotateGetter
注释的字段,但我不知道如何 select 一个方法对应于用@AnnotateGetter
注释的字段
- 其他一些AOP框架
- 编写我自己的
javax.annotation.processing.Processor
调用一些可以向方法添加注释的库。这种图书馆的最佳选择是什么?它是否必须在 javac 编译源文件之后操作字节码,或者我是否可以在生成 class 文件之前以某种方式连接到 javac 并在编译期间添加注释? - 其他...
您可以试试我的图书馆 Byte Buddy。您可以在应用程序启动时的构建过程中 运行 它,甚至可以从 Java 代理。使用最新版本可以按如下方式创建注释:
DynamicType.Unloaded<?> type = new ByteBuddy()
.makeAnnotation()
.name("AnnotateGetter")
.annotateType(new Target() {
public ElementType value() { return ElementType.FIELD; }
public Class<? extends Annotation> annotationType() { return Target.class; }
}).defineMethod("value",
SomeAnnotation.class,
Collections.emptyList(),
Visibility.PUBLIC)
.withoutCode()
.make();
然后您可以通过添加此生成注释的实例来创建或操作现有的 类。特定领域的语言保持相似。请查阅 detailed introduction 库的教程。
这是一个通过 AspectJ 使用 APT(注释处理工具)的解决方案。它将指定的注释添加到 getter 方法,但不会从字段中删除它们。所以这是一个 "copy" 动作,而不是 "move".
注释处理支持已添加到 AspectJ 版本 1.8.2 中,并在 release notes. Here is some self-consistent sample code. I compiled it from the command line because from Eclipse I failed to get it running according to AspectJ maintainer Andy Clement's description.
中进行了描述好吧,假设我们有一个(Eclipse 或其他)项目目录,目录布局如下:
SO_AJ_APT_MoveAnnotationsFromMemberToGetter
compile_run.bat
src
de/scrum_master/app/Person.java
src_apt
de/scrum_master/app/AnnotatedGetterProcessor.java
de/scrum_master/app/AnnotateGetter.java
de/scrum_master/app/CollationType.java
de/scrum_master/app/SortOrder.java
de/scrum_master/app/Unique.java
META-INF/services/javax.annotation.processing.Processor
src和src_apt都是源目录,compile_run.bat 是一个 Windows 分两个阶段构建项目的批处理文件(首先是注释处理器,然后是项目的其余部分)和 运行 最终结果以证明它确实做了它应该做的.
用于字段的注释,稍后复制到 getter 方法:
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface Unique {}
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface SortOrder {
String value() default "ascending";
}
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface CollationType {
String value() default "alphabetical";
String language() default "EN";
}
元注释指定要复制到 getter 方法的字段注释:
请注意,此元注释仅用于注释处理,因此具有 SOURCE
保留范围。
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AnnotateGetter {
Class<? extends Annotation>[] value();
}
驱动申请:
注意事项:
有四个带注解的字段(id,firstName,lastName, fieldWithoutGetter), 但只有前三个有对应的 getter 方法,最后一个没有。因此,我们希望 fieldWithoutGetter 稍后能够妥善处理,稍后通过 APT 生成空的或没有 ITD 方面。
class
Person
上的元注释@AnnotateGetter({ Unique.class, SortOrder.class, CollationType.class })
指定要考虑将哪些注释复制到 getter 方法。稍后您可以尝试一下,看看如果删除其中任何一个,结果会如何变化。我们还有一些虚拟方法
doSomething()
和doSomethingElse()
,它们应该不受以后任何注释复制的影响,即它们不应该通过 AspectJ 获得任何新注释。 (有一个否定的测试用例总是好的。)main(..)
方法使用反射来打印所有字段和方法,包括它们的注释。
package de.scrum_master.app;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@AnnotateGetter({ Unique.class, SortOrder.class, CollationType.class })
public class Person {
@Unique
private final int id;
@SortOrder("descending")
@CollationType("alphabetical")
private final String firstName;
@SortOrder("random")
@CollationType(value = "alphanumeric", language = "DE")
private final String lastName;
@SortOrder("ascending")
@CollationType(value = "numeric")
private final int fieldWithoutGetter;
public Person(int id, String firstName, String lastName, int fieldWithoutGetter) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.fieldWithoutGetter = fieldWithoutGetter;
}
public int getId() { return id; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public void doSomething() {}
public void doSomethingElse() {}
public static void main(String[] args) {
System.out.println("Field annotations:");
for (Field field : Person.class.getDeclaredFields()) {
System.out.println(" " + field.getName());
for (Annotation annotation : field.getAnnotations())
System.out.println(" " + annotation);
}
System.out.println();
System.out.println("Method annotations:");
for (Method method : Person.class.getDeclaredMethods()) {
System.out.println(" " + method.getName());
for (Annotation annotation : method.getAnnotations())
System.out.println(" " + annotation);
}
}
}
没有 APT + AspectJ 的控制台输出:
如您所见,打印了字段注释,但没有打印方法注释,因为我们尚未定义注释处理器(见下文)。
Field annotations:
id
@de.scrum_master.app.Unique()
firstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
lastName
@de.scrum_master.app.SortOrder(value=random)
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
fieldWithoutGetter
@de.scrum_master.app.SortOrder(value=ascending)
@de.scrum_master.app.CollationType(value=numeric, language=EN)
Method annotations:
main
getId
doSomething
doSomethingElse
getFirstName
getLastName
注释处理器:
现在我们需要一个注释处理器,为每个要复制的字段和注释组合生成一个方面。这样的一个方面应该是这样的:
package de.scrum_master.app;
public aspect AnnotateGetterAspect_Person_CollationType_lastName {
declare @method : * Person.getLastName() : @de.scrum_master.app.CollationType(value = "alphanumeric", language = "DE");
}
很简单,不是吗?注释处理器应将这些方面生成到目录 .apt_generated 中。 AspectJ 编译器会为我们处理这些,我们稍后会看到。但首先是注解处理器(抱歉代码太长,但这正是您所要求的):
package de.scrum_master.app;
import java.io.*;
import java.util.*;
import javax.tools.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotatedGetterProcessor extends AbstractProcessor {
private Filer filer;
@Override
public void init(ProcessingEnvironment env) {
filer = env.getFiler();
}
@SuppressWarnings("unchecked")
@Override
public boolean process(
Set<? extends TypeElement> elements,
RoundEnvironment env
) {
// Get classes annotated with something like: @AnnotateGetter({ Foo.class, Bar.class, Zot.class })
env.getElementsAnnotatedWith(AnnotateGetter.class)
.stream()
.filter(annotatedClass -> annotatedClass.getKind() == ElementKind.CLASS)
// For each filtered class, copy designated field annotations to corresponding getter method, if present
.forEach(annotatedClass -> {
String packageName = annotatedClass.getEnclosingElement().toString().substring(8);
String className = annotatedClass.getSimpleName().toString();
/*
* Unfortunately when we do something like this:
* AnnotateGetter annotateGetter = annotatedClass.getAnnotation(AnnotateGetter.class);
* Class<? extends Annotation> annotationToBeConverted = annotateGetter.value()[0];
* We will get this exception:
* Internal compiler error:
* javax.lang.model.type.MirroredTypesException:
* Attempt to access Class objects for TypeMirrors
* [de.scrum_master.app.Unique, de.scrum_master.app.SortOrder, de.scrum_master.app.CollationType]
* at org.aspectj.org.eclipse.jdt.internal.compiler.apt.model.AnnotationMirrorImpl.getReflectionValue
*
* Thus, we have to use annotation mirrors instead of annotation classes directly,
* then tediously extracting annotation values from a nested data structure. :-(
*/
// Find @AnnotateGetter annotation and extract its array of values from deep within
((List<? extends AnnotationValue>) annotatedClass.getAnnotationMirrors()
.stream()
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(AnnotateGetter.class.getName()))
.map(AnnotationMirror::getElementValues)
.map(Map::values)
.findFirst()
.get()
.stream()
.map(AnnotationValue::getValue)
.findFirst()
.get()
)
.stream()
.map(annotationValueToBeCopied -> (TypeElement) ((DeclaredType) annotationValueToBeCopied.getValue()).asElement())
// For each annotation to be copied, get all correspondingly annotated fields
.forEach(annotationTypeElementToBeCopied -> {
env.getElementsAnnotatedWith(annotationTypeElementToBeCopied)
.stream()
.filter(annotatedField -> ((Element) annotatedField).getKind() == ElementKind.FIELD)
// For each annotated field create an ITD aspect
.forEach(annotatedField -> {
String fieldName = annotatedField.getSimpleName().toString();
String aspectName =
"AnnotateGetterAspect_" + className + "_" +
annotationTypeElementToBeCopied.getSimpleName() + "_" + fieldName;
StringBuilder annotationDeclaration = new StringBuilder()
.append("@" + annotationTypeElementToBeCopied.getQualifiedName() + "(");
annotatedField.getAnnotationMirrors()
.stream()
.filter(annotationMirror -> annotationMirror.getAnnotationType().toString().equals(annotationTypeElementToBeCopied.getQualifiedName().toString()))
.map(AnnotationMirror::getElementValues)
.forEach(annotationParameters -> {
annotationParameters.entrySet()
.stream()
.forEach(annotationParameter -> {
ExecutableElement annotationParameterType = annotationParameter.getKey();
AnnotationValue annotationParameterValue = annotationParameter.getValue();
annotationDeclaration.append(annotationParameterType.getSimpleName() + " = ");
if (annotationParameterType.getReturnType().toString().equals("java.lang.String"))
annotationDeclaration.append("\"" + annotationParameterValue + "\"");
else
annotationDeclaration.append(annotationParameterValue);
annotationDeclaration.append(", ");
});
if (!annotationParameters.entrySet().isEmpty())
annotationDeclaration.setLength(annotationDeclaration.length() - 2);
annotationDeclaration.append(")");
});
// For each field with the current annotation, create an ITD aspect
// adding the same annotation to the member's getter method
String aspectSource = createAspectSource(
annotatedClass, packageName, className,
annotationDeclaration.toString(), fieldName, aspectName
);
writeAspectSourceToDisk(packageName, aspectName, aspectSource);
});
});
});
return true;
}
private String createAspectSource(
Element parentElement,
String packageName,
String className,
String annotationDeclaration,
String fieldName,
String aspectName
) {
String getterMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
StringBuilder aspectSource = new StringBuilder()
.append("package " + packageName + ";\n\n")
.append("public aspect " + aspectName + " {\n");
for (Element childElement : parentElement.getEnclosedElements()) {
// Search for methods
if (childElement.getKind() != ElementKind.METHOD)
continue;
ExecutableElement method = (ExecutableElement) childElement;
// Search for correct getter method name
if (!method.getSimpleName().toString().equals(getterMethodName))
continue;
// Parameter list for a getter method must be empty
if (!method.getParameters().isEmpty())
continue;
// Getter method must be public
if (!method.getModifiers().contains(Modifier.PUBLIC))
continue;
// Getter method must be non-static
if (method.getModifiers().contains(Modifier.STATIC))
continue;
// Add call to found method
aspectSource.append(
" declare @method : * " + className + "." + getterMethodName + "() : " +
annotationDeclaration + ";\n"
);
}
aspectSource.append("}\n");
return aspectSource.toString();
}
private void writeAspectSourceToDisk(
String packageName,
String aspectName,
String aspectSource
) {
try {
JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName);
file.openWriter().append(aspectSource).close();
System.out.println("Generated aspect " + packageName + "." + aspectName);
} catch (IOException ioe) {
// Message "already created" can appear if processor runs more than once
if (!ioe.getMessage().contains("already created"))
ioe.printStackTrace();
}
}
}
注解处理器我就不多说了,请仔细阅读。我还添加了一些源代码注释,希望它们足以被理解。
src_apt/META-INF/services/javax.annotation.processing.Processor:
我们需要此文件供注释处理器稍后与 AspectJ 编译器 (ajc) 结合使用。
de.scrum_master.app.AnnotatedGetterProcessor
批处理文件构建和运行宁项目:
抱歉,如果这是特定于平台的,但我想您可以轻松地将其转换为 UNIX/Linux shell 脚本,非常简单。
@echo off
set SRC_PATH=C:\Users\Alexander\Documents\java-src
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ
echo Building annotation processor
cd "%SRC_PATH%\SO_AJ_APT_MoveAnnotationsFromMemberToGetter"
rmdir /s /q bin
del /q processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src_apt -d bin -cp "%ASPECTJ_HOME%\lib\aspectjrt.jar"
jar -cvf processor.jar -C src_apt META-INF -C bin .
echo.
echo Generating aspects and building project
rmdir /s /q bin .apt_generated
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src -d bin -s .apt_generated -inpath processor.jar -cp "%ASPECTJ_HOME%\lib\aspectjrt.jar";processor.jar -showWeaveInfo
echo.
echo Running de.scrum_master.app.Person
java -cp bin;"%ASPECTJ_HOME%\lib\aspectjrt.jar" de.scrum_master.app.Person
构建控制台日志 + 运行 进程:
构建处理器+注释classes,然后将它们打包成processor.jar:
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 45) (aus = 46)(-2 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/AnnotatedGetterProcessor.class wird hinzugefügt(ein = 8065) (aus = 3495)(56 % verkleinert)
de/scrum_master/app/AnnotateGetter.class wird hinzugefügt(ein = 508) (aus = 287)(43 % verkleinert)
de/scrum_master/app/CollationType.class wird hinzugefügt(ein = 520) (aus = 316)(39 % verkleinert)
de/scrum_master/app/SortOrder.class wird hinzugefügt(ein = 476) (aus = 296)(37 % verkleinert)
de/scrum_master/app/Unique.class wird hinzugefügt(ein = 398) (aus = 248)(37 % verkleinert)
Aspect 生成 + 项目构建(由于其内置的注释处理支持,仅需一次 AspectJ 编译器调用即可完成):
Generating aspects and building project
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_Unique_id
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_fieldWithoutGetter
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_firstName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_lastName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_fieldWithoutGetter
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_firstName
Generated aspect de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_lastName
'public int de.scrum_master.app.Person.getId()' (Person.java:31) is annotated with @de.scrum_master.app.Unique method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_Unique_id' (AnnotateGetterAspect_Person_Unique_id.java:4)
'public java.lang.String de.scrum_master.app.Person.getFirstName()' (Person.java:32) is annotated with @de.scrum_master.app.SortOrder method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_firstName' (AnnotateGetterAspect_Person_SortOrder_firstName.java:4)
'public java.lang.String de.scrum_master.app.Person.getFirstName()' (Person.java:32) is annotated with @de.scrum_master.app.CollationType method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_firstName' (AnnotateGetterAspect_Person_CollationType_firstName.java:4)
'public java.lang.String de.scrum_master.app.Person.getLastName()' (Person.java:33) is annotated with @de.scrum_master.app.CollationType method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_CollationType_lastName' (AnnotateGetterAspect_Person_CollationType_lastName.java:4)
'public java.lang.String de.scrum_master.app.Person.getLastName()' (Person.java:33) is annotated with @de.scrum_master.app.SortOrder method annotation from 'de.scrum_master.app.AnnotateGetterAspect_Person_SortOrder_lastName' (AnnotateGetterAspect_Person_SortOrder_lastName.java:4)
最后但同样重要的是,我们再次 运行 驱动程序应用程序。这次我们应该看到从带注释的字段复制到相应的 getter 方法(如果存在这样的方法)的注释:
Running de.scrum_master.app.Person
Field annotations:
id
@de.scrum_master.app.Unique()
firstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
lastName
@de.scrum_master.app.SortOrder(value=random)
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
fieldWithoutGetter
@de.scrum_master.app.SortOrder(value=ascending)
@de.scrum_master.app.CollationType(value=numeric, language=EN)
Method annotations:
main
getId
@de.scrum_master.app.Unique()
doSomethingElse
getLastName
@de.scrum_master.app.CollationType(value=alphanumeric, language=DE)
@de.scrum_master.app.SortOrder(value=random)
getFirstName
@de.scrum_master.app.SortOrder(value=descending)
@de.scrum_master.app.CollationType(value=alphabetical, language=EN)
doSomething
瞧!享受并随时提问。 :-)
更新(2015-05-03):注意,在我的注解处理器代码中,我最初忘记也复制注解参数值,因此只创建了默认值每个注释。我刚刚修复了这个问题,使注释处理器代码变得更加冗长。因为我想让它值得重构代码并从中学习一些东西,即使你已经接受了原始答案并以另一种方式解决了你的问题,我还是玩了 Java 8 种东西,比如 lambdas,流,过滤器,地图。如果这个概念对您来说是新的,特别是对于嵌套的 forEach
循环,这不是特别可读,但我想尝试看看我能用它走多远。 ;-)