-
process(Set<? extends TypeElement> annoations, RoundEnvironment env)
:这类似于每个处理器的main()
方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment
参数,你可以查询被特定注解标注的元素。 -
getSupportedAnnotationTypes()
:在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String
集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。 -
getSupportedSourceVersion()
: 用来指定你使用的java
版本。通常你应该返回SourceVersion.latestSupported()
。不过,如果你有足够的理由坚持用java 8
的话,你也可以返回SourceVersion.RELEASE_8
。我建议使用SourceVersion.latestSupported()
。
JDK
提供了一个实现Processor
接口的抽象类AbstractProcessor
。该抽象类实现了init
、getSupportedAnnotationTypes
和getSupportedSourceVersion
方法。
它的子类可以通过@SupportedAnnotationTypes
和@SupportedSourceVersio
n注解来声明所支持的注解类型以及 Java
版本。
下面这段代码便是@CheckGetter注解处理器的实现。由于我使用了 Java 8
的编译器,因此将支持版本设置为SourceVersion.RELEASE_8
。
package com.jvm;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes(“com.jvm.CheckGetter”)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CheckGetterProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// TODO: annotated ElementKind.FIELD
for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(CheckGetter.class))) {
for (VariableElement field : ElementFilter.fieldsIn(annotatedClass.getEnclosedElements())) {
if (!containsGetter(annotatedClass, field.getSimpleName().toString())) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
String.format(“getter not found for ‘%s.%s’.”, annotatedClass.getSimpleName(), field.getSimpleName()));
}
}
}
return false;
}
private boolean containsGetter(TypeElement typeElement, String name) {
String getter = “get” + name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
for (ExecutableElement executableElement : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
if (!executableElement.getModifiers().contains(Modifier.STATIC)
&& executableElement.getSimpleName().toString().equals(getter)
&& executableElement.getParameters().isEmpty()) {
return true;
}
}
return false;
}
}
该注解处理器仅重写了process
方法。这个方法将接收两个参数,分别代表该注解处理器所能处理的注解类型,以及囊括当前轮生成的抽象语法树的RoundEnvironment
。
由于该处理器针对的注解仅有@CheckGetter一个,而且我们并不会读取注解中的值,因此第一个参数并不重要。在代码中,我直接使用了 roundEnv.getElementsAnnotatedWith(CheckGetter.class)
来获取所有被@CheckGetter
注解的类(以及字段)。
process
方法涉及各种不同类型的Element
,分别指代 Java
程序中的各个结构。如TypeElement
指代类或者接口,VariableElement
指代字段、局部变量、enum
常量等,ExecutableElement
指代方法或者构造器。
package foo; // PackageElement
class Foo { // TypeElement
int a; // VariableElement
static int b; // VariableElement
Foo () {} // ExecutableElement
void setA ( // ExecutableElement
int newA // VariableElement
) {}
}
我们可以通过TypeElement.getEnclosedElements
方法,获得上面这段代码中Foo
类的字段、构造器以及方法。
我们也可以通过ExecutableElement.getParameters
方法,获得setA
方法的参数。具体这些Element
类都有哪些 API
,你可以参考它们的 Javado。
在将该注解处理器编译成 class
文件后,我们便可以将其注册为 Java
编译器的插件,并用来处理其他源代码。
注册的方法主要有两种。第一种是直接使用 javac
命令的-processor
参数,如下所示:
javac -cp /CLASSPATH/TO/CheckGetterProcessor -processor com.jvm.CheckGetterProcessor Foo.java
error: Class ‘Foo’ is annotated as @CheckGetter, but field ‘a’ is without getter
1 error
第二种则是将注解处理器编译生成的 class
文件压缩入 jar
包中,并在 jar
包的配置文件中记录该注解处理器的包名及类名,即com.jvm.CheckGetterProcessor
。
(具体路径及配置文件名为META-INF/services/javax.annotation.processing.Processor
)
当启动 Java
编译器时,它会寻找 classpath
路径上的 jar
包是否包含上述配置文件,并自动注册其中记录的注解处理器。
$ javac -cp /PATH/TO/CheckGetterProcessor.jar Foo.java
error: Class ‘Foo’ is annotated as @CheckGetter, but field ‘a’ is without getter
1 error
此外,我们还可以在 IDE 中配置注解处理器。这里我就不过多演示了,感兴趣的小伙伴可以自行搜索。
前面提到,注解处理器可以用来修改已有源代码或者生成源代码。
确切地说,注解处理器并不能真正地修改已有源代码。这里指的是修改由 Java 源代码生成的抽象语法树,在其中修改已有树节点或者插入新的树节点,从而使生成的字节码发生变化。
对抽象语法树的修改涉及了 Java 编译器的内部 API,这部分很可能随着版本变更而失效。因此,我并不推荐这种修改方式。
我们用注解处理器来生成源代码则比较常用。我们以前介绍过的压力测试 jcstress
,以及接下来即将介绍的 JMH
工具,都是依赖这种方式来生成测试代码的。
package com.jvm;
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.SOURCE)
public @interface Adapt {
Class<?> value();
}
在上面这段代码中,我定义了一个注解@Adapt
。这个注解将接收一个Class
类型的参数value
(如果注解类仅包含一个名为value
的参数时,那么在使用注解时,我们可以省略value
=),具体用法如这段代码所示。
package com.jvm;
import java.util.function.IntBinaryOperator;
public class Bar {
@Adapt(IntBinaryOperator.class)
public static int add(int a, int b) {
return a + b;
}
}
接下来,我们来实现一个处理@Adapt
注解的处理器。该处理器将生成一个新的源文件,实现参数value
所指定的接口,并且调用至被该注解所标注的方法之中。
package com.jvm;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
@SupportedAnnotationTypes(“com.jvm.Adapt”)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AdaptProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
if (!“com.jvm.Adapt”.equals(annotation.getQualifiedName().toString())) {
continue;
}
ExecutableElement targetAsKey = getExecutable(annotation, “value”);
for (ExecutableElement annotatedMethod : ElementFilter.methodsIn(roundEnv.getElementsAnnotatedWith(annotation))) {
if (!annotatedMethod.getModifiers().contains(Modifier.PUBLIC)) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, “@Adapt on non-public method”);
continue;
}
if (!annotatedMethod.getModifiers().contains(Modifier.STATIC)) {
// TODO support non-static methods
continue;
}
TypeElement targetInterface = getAnnotationValueAsTypeElement(annotatedMethod, annotation, targetAsKey);
if (targetInterface.getKind() != ElementKind.INTERFACE) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, “@Adapt with non-interface input”);
continue;
}
TypeElement enclosingType = getTopLevelEnclosingType(annotatedMethod);
createAdapter(enclosingType, annotatedMethod, targetInterface);
}
}
return true;
}
private void createAdapter(TypeElement enclosingClass, ExecutableElement annotatedMethod,
TypeElement targetInterface) {
PackageElement packageElement = (PackageElement) enclosingClass.getEnclosingElement();
String packageName = packageElement.getQualifiedName().toString();
String className = enclosingClass.getSimpleName().toString();
String methodName = annotatedMethod.getSimpleName().toString();
String adapterName = className + “_” + methodName + “Adapter”;
ExecutableElement overriddenMethod = getFirstNonDefaultExecutable(targetInterface);
try {
Filer filer = processingEnv.getFiler();
JavaFileObject sourceFile = filer.createSourceFile(packageName + “.” + adapterName, new Element[0]);
try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) {
out.println("package " + packageName + “;”);
out.println("import " + targetInterface.getQualifiedName() + “;”);
out.println();
out.println(“public class " + adapterName + " implements " + targetInterface.getSimpleName() + " {”);
out.println(" @Override");
out.println(" public " + overriddenMethod.getReturnType() + " " + overriddenMethod.getSimpleName()
- formatParameter(overriddenMethod, true) + " {");
out.println(" return " + className + “.” + methodName + formatParameter(overriddenMethod, false) + “;”);
out.println(" }");
out.println(“}”);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private ExecutableElement getExecutable(TypeElement annotation, String methodName) {
for (ExecutableElement method : ElementFilter.methodsIn(annotation.getEnclosedElements())) {
if (methodName.equals(method.getSimpleName().toString())) {
return method;
}
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, “Incompatible @Adapt.”);
return null;
}
private ExecutableElement getFirstNonDefaultExecutable(TypeElement annotation) {
for (ExecutableElement method : ElementFilter.methodsIn(annotation.getEnclosedElements())) {
if (!method.isDefault()) {
return method;
}
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
“Target interface should declare at least one non-default method.”);
return null;
}
private TypeElement getAnnotationValueAsTypeElement(ExecutableElement annotatedMethod, TypeElement annotation,
ExecutableElement annotationFunction) {
TypeMirror annotationType = annotation.asType();
for (AnnotationMirror annotationMirror : annotatedMethod.getAnnotationMirrors()) {
if (processingEnv.getTypeUtils().isSameType(annotationMirror.getAnnotationType(), annotationType)) {
AnnotationValue value = annotationMirror.getElementValues().get(annotationFunction);
if (value == null) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, “Unknown @Adapt target”);
continue;
}
TypeMirror targetInterfaceTypeMirror = (TypeMirror) value.getValue();
return (TypeElement) processingEnv.getTypeUtils().asElement(targetInterfaceTypeMirror);
}
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, “@Adapt should contain target()”);
return null;
}
private TypeElement getTopLevelEnclosingType(ExecutableElement annotatedMethod) {
TypeElement enclosingType = null;
Element enclosing = annotatedMethod.getEnclosingElement();
while (enclosing != null) {
if (enclosing.getKind() == ElementKind.CLASS) {
enclosingType = (TypeElement) enclosing;
} else if (enclosing.getKind() == ElementKind.PACKAGE) {
最后
一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。
这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。
请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析
singType = (TypeElement) enclosing;
} else if (enclosing.getKind() == ElementKind.PACKAGE) {
最后
一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。
这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。
[外链图片转存中…(img-SbJ58MKd-1714204927272)]
请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析