Java Pluggable Annotation Processing

不想偷懒的程序员不是好的程序员。减少重复的劳动是编程必备的技能,运用pluggable annotation processing 能在编译时期通过注解生成代码达到减少重复代码的编写,比如单例类,工厂类,各种model相关的vo,dto等等。让我们从一个小demo开始吧!!

在IDEA创建基于maven的程序

  • annotation模块定义各种注解
  • proccessor模块定义注解的处理类
  • test用于测试
    在annotation模块中定义一个注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.CLASS)
    public @interface AutoNewInterface {
    /**
    * 接口名
    * @return
    */
    String value() default "";

    /**
    * 接口所在包
    * @return
    */
    String packageName() default "" ;

    }

在processor模块中定义注解的处理类

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package com.hzy.common.processors;

import com.hzy.common.annotations.AutoNewInterface;
import com.squareup.javapoet.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
* @author huangyang
* @Description: AutoNewService 注解处理器
* @date 2019/05/08 下午3:39
*/
public class AutoNewInterfaceProcessor extends AbstractProcessor {

private Types typeUtils;
private Elements elementUtils;
private Filer filer;
//用于错误信息
private Messager messager;


private static boolean ifAdded(ExecutableElement method) {
Set<Modifier> modifiers = method.getModifiers();
if (modifiers.contains(Modifier.PUBLIC) && method.getAnnotation(Override.class) == null) {
return true;
}
return false;
}

private static JavaFile newInterface(String packageName, String name, List<ExecutableElement> methods) throws ClassNotFoundException {

TypeSpec.Builder builder = TypeSpec.interfaceBuilder(name).addModifiers(Modifier.PUBLIC);
for (ExecutableElement method : methods) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(method.getSimpleName().toString());
methodBuilder.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
methodBuilder.returns(ClassName.get(method.getReturnType()));
List<? extends VariableElement> parameters = method.getParameters();
for (VariableElement p : parameters) {
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(p.asType()), p.getSimpleName().toString(), null).build();
methodBuilder.addParameter(parameterSpec);
}
builder.addMethod(methodBuilder.build());
}
return JavaFile.builder(packageName, builder.build()).build();

}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

for (TypeElement te : annotations) {

Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(te);

if (elements == null || elements.isEmpty()) {
return false;
}

for (Element element : elements) {

ElementKind kind = element.getKind();
if (kind != ElementKind.CLASS) {
error(element, "注解" + te.getSimpleName().toString() + "只能应用在类上");
return false;
}

AutoNewInterface annotation = element.getAnnotation(AutoNewInterface.class);

String name = interfaceName((TypeElement) element, annotation.value());
String packageName = packageName((TypeElement) element, annotation.packageName());

List<ExecutableElement> methods = ElementFilter.methodsIn(element.getEnclosedElements());

if (methods == null) {
return false;
}
List<ExecutableElement> toBeAddMethods = methods.stream().filter(f -> ifAdded(f)).collect(Collectors.toList());
if (toBeAddMethods.isEmpty()) {
return false;
}
try {
JavaFile javaFile = newInterface(packageName, name, toBeAddMethods);
javaFile.writeTo(filer);
} catch (Exception e) {
error(element, "自动创建接口[%s]异常,INFO: %s", packageName + "." + name, e.toString());
return false;
}
}
}
return true;
}

@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> sets = new HashSet<>();
sets.add(AutoNewInterface.class.getCanonicalName());
return sets;
}

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}

private String interfaceName(TypeElement element, String name) {
if (name == null || "".equalsIgnoreCase(name)) {
String simpleName = element.getSimpleName().toString();
if (simpleName.endsWith("Impl")) {
return simpleName.replace("Impl", "");
} else {
return "I" + simpleName;
}
}
return name;
}

private String packageName(TypeElement element, String name) {
if (name == null || "".equalsIgnoreCase(name)) {
return elementUtils.getPackageOf(element).getQualifiedName().toString();
}
return name;
}


private void error(Element e, String msg, Object... args) {
messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
}
}

注册处理器
1
2
1: 在processor项目的resources资源目录下创建META-INF/services目录
2: 创建javax.annotation.processing.Processor文件,文件内容com.hzy.common.processors.AutoNewInterfaceProcessor
编译processor模块

编译时会报找不到注册的处理器,原因是编译时编译器会加载处理器来处理注解,此时处理器还未被编译,因此要在pom中配置如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.source}</target>
<!-- Disable annotation processing for ourselves.-->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>

debug

在test模块中编写代码测试注解处理器

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
package com.hzy.common.test;

import com.hzy.common.annotations.AutoNewInterface;

/**
* @author huangyang
* @Description: ${todo}(这里用一句话描述这个类的作用)
* @date 2019/05/08 下午5:34
*/
@AutoNewInterface
public class TestServiceImpl {

public void test(){}

public Integer test0(){
return 0;
}
public int test1(){
return 0;
}
public byte test2(){
return 0;
}
public byte[] test3(){
return null;
}
public Byte[] test4(){
return null;
}

protected void test(int a){

}


public Test test5(){
return null;
}


}

因为注解处理器是在编译阶段被执行的,而且是在单独的jvm中运行,不能直接在idea中测试。可以通过mvnDebug + idea remote远程 调试

  • 在终端运行 mvnDebug
  • 在idea中创建一个remote ,port修改为mvnDebug监听的端口,并运行
  • 在注解器中打断点

编译通过的话在test/target/generated-source/annotations目录下能看到动态生成的类文件

坚持原创技术分享,您的支持将鼓励我继续创作!