JAVA编译时注解

"你好,未来"

Posted by one_cup on 2016-12-27

JAVA编译时注解

最近看了几个6.0权限的库,发现一个黑科技,编译时注解,简单了解了一下之后发现,简直就是懒人专用的装逼神器啊,赶紧学一学。

自定义注解相关知识

定义注解的格式:

public @interface 注解名{定义体}```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
常用的元注解:
**@Retention**
1. RetentionPolicy.SOURCE 源码时注解,一般用来作为编译器标记。就比如Override, Deprecated, SuppressWarnings这样的注解。(这个我们一般都很少自定义的)
2. RetentionPolicy.RUNTIME 运行时注解,一般在运行时通过反射去识别的注解。
3. RetentionPolicy.CLASS 编译时注解,在编译时处理。
**@Target**
1. ElementType.TYPE 接口、类、枚举、注解
2. ElementType.FIELD 字段、枚举的常量
3. ElementType.METHOD 方法
4. ElementType.PARAMETER 方法参数
5. ElementType.CONSTRUCTOR 构造函数
6. ElementType.LOCAL_VARIABLE 局部变量
7. ElementType.ANNOTATION_TYPE 注解
8. ElementType.package
还有其他的注解,平时不用,也不会就不介绍了。
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ShowRequestPermissionRationale
{
int value();
}

上面的代码来自鸿洋大神的MPermission,可以看到是一个编译时的方法注解,其中有一个参数。

注解处理器

当我们写好一个注解之后,怎么获取并处理这个注解所对应的数据呢?这就需要一个注解处理器AbstractProcessor,我们可以通过它来获取我们自定义的注解,并且获取数据,生成代码。常见方法:
getSupportedAnnotationTypes()
返回一个set集合,集合内容为自定义注解的包名+类名

1
2
3
4
5
6
7
8
9
@Override
public Set<String> getSupportedAnnotationTypes()
{
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(PermissionDenied.class.getCanonicalName());
supportTypes.add(PermissionGrant.class.getCanonicalName());
supportTypes.add(ShowRequestPermissionRationale.class.getCanonicalName());
return supportTypes;
}

其中(PermissionDenied.class.getCanonicalName()是获取当前class的包名加类名。
getSupportedSourceVersion():返回支持的版本,这个统一返回latestSupported();
init(ProcessingEnvironment processingEnv):初始化方法,可以通过processingEnv获取相关对象,对元素进行操作。
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv): 参数 Set<? extends TypeElement> annotations :将返回所有的由该Processor处理,并待处理的 Annotations。(属于该Processor处理的注解,但并未被使用,不存在与这个集合里)
参数 RoundEnvironment roundEnv :表示当前或是之前的运行环境,可以通过该对象查找找到的注解。返回值 表示这组 annotations 是否被这个 Processor 接受,如果接受(true)后续子的 pocessor 不会再对这个 Annotations 进行处理。
相关对象:
Messager:控制台输出
Elements:获取元素的相关信息
Filer:获取文件操作对象
Element:表示一个程序元素,比如包、类或者方法。子类类型:

  1. ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。对应@Target(ElementType.METHOD)@Target(ElementType.CONSTRUCTOR)
  2. PackageElement:表示一个包程序元素。提供对有关包极其成员的信息访问。对应@Target(ElementType.PACKAGE)
  3. TypeElement:表示一个类或接口程序元素。提供对有关类型极其成员的信息访问。对应@Target(ElementType.TYPE)
  4. TypeParameterElement:表示一般类、接口、方法或构造方法元素的类型参数。对应@Target(ElementType.PARAMETER)
  5. VariableElement:表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数。对应@Target(ElementType.LOCAL_VARIABLE)
    相应的Target可以进行强转。
    获取参数:根据不同的Element获取一些基本信息:包名,类名,方法名,参数类型,返回值。
    ExecutableElement
    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
    //OnceClick.class 以 @Target(ElementType.METHOD)修饰
    for (Element element : roundEnv.getElementsAnnotatedWith(OnceClick.class)) {
    //对于Element直接强转
    ExecutableElement executableElement = (ExecutableElement) element;
    //非对应的Element,通过getEnclosingElement转换获取
    TypeElement classElement = (TypeElement) element
    .getEnclosingElement();
    //当(ExecutableElement) element成立时,使用(PackageElement) element
    // .getEnclosingElement();将报错。
    //需要使用elementUtils来获取
    Elements elementUtils = processingEnv.getElementUtils();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);
    //全类名
    String fullClassName = classElement.getQualifiedName().toString();
    //类名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //方法名
    String methodName = executableElement.getSimpleName().toString();
    //取得方法参数列表
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    //参数类型列表
    List<String> types = new ArrayList<>();
    for (VariableElement variableElement : methodParameters) {
    TypeMirror methodParameterType = variableElement.asType();
    if (methodParameterType instanceof TypeVariable) {
    TypeVariable typeVariable = (TypeVariable) methodParameterType;
    methodParameterType = typeVariable.getUpperBound();
    }
    //参数名
    String parameterName = variableElement.getSimpleName().toString();
    //参数类型
    String parameteKind = methodParameterType.toString();
    types.add(methodParameterType.toString());
    }
    }

VariableElement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
//ElementType.FIELD注解可以直接强转VariableElement
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//类成员名
String variableName = variableElement.getSimpleName().toString();
//类成员类型
TypeMirror typeMirror = variableElement.asType();
String type = typeMirror.toString();
}

TypeElement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (Element element : roundEnv.getElementsAnnotatedWith(xxx.class)) {
//ElementType.TYPE注解可以直接强转TypeElement
TypeElement classElement = (TypeElement) element;
PackageElement packageElement = (PackageElement) element
.getEnclosingElement();
//全类名
String fullClassName = classElement.getQualifiedName().toString();
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//父类名
String superClassName = classElement.getSuperclass().toString();
}

了解了这么多之后,来实战检验一下吧,还是一MPermission为例

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
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
{
//清空集合
mProxyMap.clear();
//打印处理器开始处理注解
messager.printMessage(Diagnostic.Kind.NOTE, "process...");
//解析指定注解
if (!processAnnotations(roundEnv, PermissionGrant.class)) return false;
if (!processAnnotations(roundEnv, PermissionDenied.class)) return false;
if (!processAnnotations(roundEnv, ShowRequestPermissionRationale.class)) return false;
//遍历Map集合中key
for (String key : mProxyMap.keySet())
{ //获得对应的Value对象
ProxyInfo proxyInfo = mProxyMap.get(key);
try
{ // 创建source文件 传入代理类名
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
proxyInfo.getProxyClassFullName(),
proxyInfo.getTypeElement());
//获取一个流对象
Writer writer = jfo.openWriter();
//写入内容
writer.write(proxyInfo.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e)
{ //打印失败信息
error(proxyInfo.getTypeElement(),
"Unable to write injector for type %s: %s",
proxyInfo.getTypeElement(), e.getMessage());
}
}
return true;
}
private boolean processAnnotations(RoundEnvironment roundEnv, Class<? extends Annotation> clazz)
{ //获得当前环境下所有的clazz注解
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(clazz))
{
//检查注解是否可用,必须是方法注解,不是Private,不是Abstract
if (!checkMethodValid(annotatedElement, clazz)) return false;
//强转成方法元素
ExecutableElement annotatedMethod = (ExecutableElement) annotatedElement;
//获取类元素
TypeElement classElement = (TypeElement) annotatedMethod.getEnclosingElement();
//获得全类名
String fqClassName = classElement.getQualifiedName().toString();
//根据全类名获取对应的代理
ProxyInfo proxyInfo = mProxyMap.get(fqClassName);
if (proxyInfo == null)
{ //为空则创建新的代理,入参是元素Util和类元素
proxyInfo = new ProxyInfo(elementUtils, classElement);
//存入
mProxyMap.put(fqClassName, proxyInfo);
//代理设置类元素
proxyInfo.setTypeElement(classElement);
}
//获得注解,比对,分发
Annotation annotation = annotatedMethod.getAnnotation(clazz);
if (annotation instanceof PermissionGrant)
{
//获得注解的Value值
int requestCode = ((PermissionGrant) annotation).value();
//将Value作为key,注解所标注的方法名作为value,写入对应的注解集合中
proxyInfo.grantMethodMap.put(requestCode, annotatedMethod.getSimpleName().toString());
} else if (annotation instanceof PermissionDenied)
{
int requestCode = ((PermissionDenied) annotation).value();
proxyInfo.deniedMethodMap.put(requestCode, annotatedMethod.getSimpleName().toString());
} else if (annotation instanceof ShowRequestPermissionRationale)
{
int requestCode = ((ShowRequestPermissionRationale) annotation).value();
proxyInfo.rationaleMethodMap.put(requestCode, annotatedMethod.getSimpleName().toString());
} else
{
error(annotatedElement, "%s not support .", clazz.getSimpleName());
return false;
}
}
return true;
}

代码里的注释写的很清楚了,不过多介绍,接下来看看proxyInfo.generateJavaCode()方法做了哪些事情

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
public String generateJavaCode()
{
StringBuilder builder = new StringBuilder();
builder.append("// Generated code. Do not modify!\n");
builder.append("package ").append(packageName).append(";\n\n");
builder.append("import com.zhy.m.permission.*;\n");
builder.append('\n');
builder.append("public class ").append(proxyClassName).append(" implements " + ProxyInfo.PROXY + "<" + typeElement.getSimpleName() + ">");
builder.append(" {\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
private void generateMethods(StringBuilder builder) {
generateGrantMethod(builder);
generateDeniedMethod(builder);
generateRationaleMethod(builder);
}
private void generateRationaleMethod(StringBuilder builder) {
builder.append("@Override\n ");
builder.append("public void rationale(" + typeElement.getSimpleName() + " source , int requestCode) {\n");
builder.append("switch(requestCode) {");
for (int code : rationaleMethodMap.keySet()) {
builder.append("case " + code + ":");
builder.append("source." + rationaleMethodMap.get(code) + "();");
builder.append("break;");
}
builder.append("}");
builder.append(" }\n");
builder.append("@Override\n ");
builder.append("public boolean needShowRationale(int requestCode) {\n");
builder.append("switch(requestCode) {");
for (int code : rationaleMethodMap.keySet()) {
builder.append("case " + code + ":");
builder.append("return true;");
}
builder.append("}\n");
builder.append("return false;");
builder.append(" }\n");
}

创建一个StringBuilder对象,拼接出一个一个类的全部内容,代码不难理解。来看看结果是什么样的

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
// Generated code. Do not modify!
package com.zhy.permission_sample;
import com.zhy.m.permission.*;
public class MainActivity$$PermissionProxy implements PermissionProxy<MainActivity> {
@Override
public void grant(MainActivity source, int requestCode) {
switch (requestCode) {
case 2:
source.requestSdcardSuccess();
break;
case 3:
source.requestCallPhoneSuccess();
break;
}
}
@Override
public void denied(MainActivity source, int requestCode) {
switch (requestCode) {
case 2:
source.requestSdcardFailed();
break;
case 3:
source.requestCallPhoneFailed();
break;
}
}
@Override
public void rationale(MainActivity source, int requestCode) {
switch (requestCode) {
case 2:
source.whyNeedSdCard();
break;
}
}
@Override
public boolean needShowRationale(int requestCode) {
switch (requestCode) {
case 2:
return true;
}
return false;
}
}

跟上文中所设计的StringBuilder一致,这样我们就通过编译时注解的方式自动生成了一个类。