注解-第四篇
程序员文章站
2022-03-14 19:20:38
...
编译时注解
编译时注解使用 RetentionPolicy.CLASS 修饰,注解会保留在java和class文件里,在执行的时候,会被虚拟机丢弃,不会加载到虚拟机中。由于使用 RetentionPolicy.CLASS 注解代码的生成发生在编译阶段,在运行时只需要正常调用生成的代码就可以,所以效率跟正常编写代码一样。
一、常用方法和类
- AbstractProcessor 中基础方法,文档
编译时注解发生在编译时,解析注解需要继承 javax.annotation.processing.AbstractProcessor 并实现 process 方法,在 process 中处理逻辑,生成代码。
/*
* 最先被注解工具调用,完成初始工作
* @param env 提供Elements等注解工具
*/
public synchronized void init(ProcessingEnvironment env) {}
/**
* 声明此Processor所支持处理的注解类
* eg:Set<String> set = new LinkedHashSet<>();set.add(xxx.class.getCanonicalName());
* 此方法可以替换为SupportedAnnotationTypes注解
*/
public Set<String> getSupportedAnnotationTypes() {}
/**
* 指定Java版本,通常这里返回SourceVersion.latestSupported(),
* 返回特定版本如JDK1.7:SourceVersion.RELEASE_7
* 默认使用 SourceVersion.RELEASE_6
* 此方法可以替换为SupportedSourceVersion注解
*/
public SourceVersion getSupportedSourceVersion(){}
/**
* 注解处理器必须要实现的方法,在这里处理注解,生成代码
* @param set 请求处理的注解
* @param env 有关当前和上一轮的信息环境
* @return 是否截获该注解(不进行进一步处理)
*/
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {}
上面init方法返回一个ProcessingEnvironment对象,看AbstractProcessor源码,他其实赋值给了一个protected修饰的processingEnv变量,说明在子类中任何地方都可以使用,ProcessingEnvironment中包括了一些工具类,辅助我们生成注解。例如操作元素实现的Elements,生成文件的Filer,输出信息的Messager(可以打印信息跟踪代码,用命令行编译会在使用时在命令行输入,用AS编译会在AS的Gradle Console中输出).
- Element 类 文档
public interface Element extends AnnotatedConstruct {
/*
* 返回此元素定义的类型
* @return TypeMirror 他的getKind()方法返回TypeKind枚举类,见下面TypeKind类
*/
TypeMirror asType();
/**
* 返回此元素所属的种类
* @return ElementKind,见下面ElementKind类
*/
ElementKind getKind();
/**
* 返回此元素的修饰符,不包括注解
* @return Set<Modifier>,为啥返回是个集合,因为一个元素的修饰符有多个,见下面Modifier类
*/
Set<Modifier> getModifiers();
/*
* 返回此元素的简单(非限定)名称
*/
Name getSimpleName();
/*
* 返回最内层元素
* 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回另一个元素.
* 如果这个元素是顶层类型,则返回其包.
* 如果这个元素是一个包,则返回 null.
* 如果这个元素是一个参数类型,则返回此参数类型的泛型元素.
*/
Element getEnclosingElement();
/*
* 返回直接出现在此元素上的注解,要获得继承的注解,请使用 getAllAnnotationMirrors
*/
List<? extends AnnotationMirror> getAnnotationMirrors();
/*
* 返回指定类型的此元素的注解
*/
<A extends Annotation> A getAnnotation(Class<A> var1);
}
- ElementKind 类 文档
public enum ElementKind {
PACKAGE, // 包
ENUM, // 枚举
CLASS, // 类
ANNOTATION_TYPE, // 注解
INTERFACE, // 接口
ENUM_CONSTANT, // 枚举常量
FIELD, // 属性
PARAMETER, // 方法或构造函数的参数
LOCAL_VARIABLE, // 局部变量
EXCEPTION_PARAMETER, // 异常参数
METHOD, // 方法
CONSTRUCTOR, // 构造函数
STATIC_INIT, // 静态初始化程序
INSTANCE_INIT, // 实例初始化程序
TYPE_PARAMETER, // 形参
OTHER, // 实现保留元素
RESOURCE_VARIABLE; // 资源变量
private ElementKind() {
}
// 是不是类
public boolean isClass() {
return this == CLASS || this == ENUM;
}
// 是不是接口
public boolean isInterface() {
return this == INTERFACE || this == ANNOTATION_TYPE;
}
// 是不是属性
public boolean isField() {
return this == FIELD || this == ENUM_CONSTANT;
}
}
- Modifier 类 文档
public enum Modifier {
PUBLIC, // public
PROTECTED, // protected
PRIVATE, // private
ABSTRACT, // abstract
DEFAULT, //
STATIC, // static
FINAL, // final
TRANSIENT, // transient
VOLATILE, // volatile
SYNCHRONIZED, // synchronized
NATIVE, // native
STRICTFP; // strictfp
}
- TypeKind 类 文档
public enum TypeKind {
BOOLEAN, // boolean
BYTE, // byte
SHORT, // short
INT, // int
LONG, // long
CHAR, // char
FLOAT, //float
DOUBLE, // double
VOID, // void
NONE, // 在没有实际类型的情况下使用的伪类型
NULL, // null
ARRAY, // array
DECLARED, // 类或接口类型
ERROR, // 无法解析的累或接口类型
TYPEVAR, // typevar
WILDCARD, // 通配符类型参数
PACKAGE, // 与包元素对应的伪类型
EXECUTABLE, // 方法,构造函数或初始化程序
OTHER, // 实现保留类型
UNION, //联合类型
INTERSECTION; // 交叉类型
}
- Element 的子类, Element 有七个子类,如下
TypeElement
表示类或接口程序元素。提供对类型及其成员的信息的访问。注意,枚举类型是一种类,注释类型是一种接口
/*
* 返回在此类或接口中直接声明的字段,方法,构造函数和成员类型
*/
List<? extends Element> getEnclosedElements();
/*
* 返回此类型元素的嵌套类型
*/
NestingKind getNestingKind();
/*
* 返回此类型元素的完全限定名称
*/
Name getQualifiedName();
/*
* 返回此类型元素的简单名称
*/
Name getSimpleName();
/*
* 返回此类型元素的直接超类
*/
TypeMirror getSuperclass();
/*
* 返回由此类直接实现或由此接口扩展的接口类型
*/
List<? extends TypeMirror> getInterfaces();
/*
* 以声明顺序返回此类型元素的形式类型参数
*/
List<? extends TypeParameterElement> getTypeParameters();
/*
* 返回*类型的包,并返回在词法上直接封装嵌套类型的元素。
*/
Element getEnclosingElement();
VariableElement
表示字段,枚举常量,方法或构造函数参数,局部变量,资源变量或异常参数
/*
* 如果这是一个初始化为编译时常量的final字段,则返回该字段。
*/
Object getConstantValue();
/*
* 返回此变量的简单名称
*/
Name getSimpleName();
/*
* 返回封装此变量的元素
*/
Element getEnclosingElement();
ExecutableElement
表示类或接口的方法,构造函数或初始化程序(静态或实例),包括注解类型元素
/*
* 以声明顺序返回形参
*/
List<? extends TypeParameterElement> getTypeParameters();
/*
* 返回类型
*/
TypeMirror getReturnType();
/*
* 返回形参
*/
List<? extends VariableElement> getParameters();
/*
* 返回接收方类型
* 作为实例方法或内部类的构造函数具有从声明类型派生的接收方类型。静态方法,或非内部类的构造函数,或者初始化器(静态或实例),没有接收方类型
*/
TypeMirror getReceiverType();
/*
* 如果此方法或构造函数接受可变数量的参数,则返回true
*/
boolean isVarArgs();
/*
* 如果此方法是默认方法,则返回true
*/
boolean isDefault();
/*
* 以声明顺序返回此方法或构造函数的throws子句中列出的异常和其他throwable
*/
List<? extends TypeMirror> getThrownTypes();
/*
* 如果是注解类型元素,则返回默认值
*/
AnnotationValue getDefaultValue();
/*
* 返回构造函数,方法或初始值设定项的简单名称
*/
Name getSimpleName();
PackageElement
表示包程序元素。提供对包及其成员的信息的访问
/*
* 返回此包的完全限定名称
*/
Name getQualifiedName();
/*
* 返回此包的简单名称
*/
Name getSimpleName();
/*
* 返回此包中的*类和接口
*/
List<? extends Element> getEnclosedElements();
/*
* 如果这是一个未命名的包,则返回true
*/
boolean isUnnamed();
/*
* 返回null
*/
Element getEnclosingElement();
TypeParameterElement
表示泛型类,接口,方法或构造函数元素的正式类型参数。类型参数声明TypeVariable
Parameterizable
具有类型参数的元素的mixin接口
QualifiedNameable
具有限定名称的元素的mixin接口
二、下面实现个例子看一下具体步骤。本Demo要实现的效果是通过在类和属性上添加注解,在编译时在电脑本地生成一个txt文件,并在文件中添加内容为类名称和属性名称和类型。
环境:MBP+AS gradle配置一下环境变量
1、新建Android工程名为 AnnotationPractice,完成后,新建一个Module选择Java Library 名为 txtlib.
2、在Java工程中定义注解类Txtization
@Documented()
@Retention(RetentionPolicy.CLASS) // 编译时注解
@Target({ElementType.TYPE, ElementType.FIELD}) // 修饰类和属性
public @interface Txtization {
}
3、在Java工程中定义注解处理器TxtProcessor
@SupportedAnnotationTypes({"com.example.bill.txtlib.Txtization"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class TxtProcessor extends AbstractProcessor {
// 注解辅助工具类
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mElementUtils = processingEnvironment.getElementUtils();
}
/*@Override
public Set<String> getSupportedAnnotationTypes() {
// 此方法和类上面注解SupportedAnnotationTypes功能一样
Set<String> set = new LinkedHashSet<>();
set.add(Txtization.class.getCanonicalName());
return set;
}*/
/*@Override
public SourceVersion getSupportedSourceVersion() {
// 此方法和类上面注解SupportedSourceVersion功能一样
return SourceVersion.latestSupported();
}*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Txtization.class);
Map<String, List<VariableElement>> map = new HashMap<>();
// 解析注解信息,并添加到map中
collectInfo(elements, map);
// 生成txt文件
generateTxt(map);
return true; // 拦截后续Processor的处理
}
// 解析收集注解信息
private void collectInfo(Set<? extends Element> elements, Map<String, List<VariableElement>> map) {
for (Element element : elements) {
// kind代表注解的类型,是个枚举类,具体代表的意义可以看一下常量名称就知道了
ElementKind kind = element.getKind();
// 由于我们定义的注解Txtization表明了只可以修饰类和属性,所以下面就可以解析这两个就可以了
if (kind == ElementKind.CLASS) { // 类
TypeElement typeElement = (TypeElement) element; // TypeElement类元素
List<? extends Element> allMembers = mElementUtils.getAllMembers(typeElement); // 获取所有类成员
String qualifiedName = typeElement.getQualifiedName().toString(); // 返回此类型元素的完全限定名称(包名+类名)
// ElementFilter.fieldsIn(allMembers) : 工具类将若有类成员转换为VariableElement类型
map.put(qualifiedName, ElementFilter.fieldsIn(allMembers));
} else if (kind == ElementKind.FIELD) { // 属性
VariableElement variableElement = (VariableElement) element; // VariableElement字段
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); // 获取相关类
String qualifiedName = typeElement.getQualifiedName().toString();
List<VariableElement> variableElementList = map.get(qualifiedName);
if (variableElementList == null) {
variableElementList = new ArrayList<>();
map.put(qualifiedName, variableElementList);
}
if (!variableElementList.contains(variableElement))
variableElementList.add(variableElement);
}
}
}
// 生成txt
private void generateTxt(Map<String, List<VariableElement>> map) {
File dir = new File("/Users/用户名/Desktop/json/"); // 生成到桌面json文件夹中
if (!dir.exists()) {
dir.mkdir();
}
for (String s : map.keySet()) {
File file = new File(dir, s.replaceAll("\\.", "_") + ".txt"); // 文件名,.替换为_
FileWriter fw = null;
try {
fw = new FileWriter(file);
fw.append("class:").append("\"" + s + "\"").append(",\n"); // s类全限定名
List<VariableElement> fields = map.get(s);
for (int i = 0; i < fields.size(); i++) {
VariableElement field = fields.get(i);
// field.getSimpleName() 属性名 field.asType() 类型
fw.append(field.getSimpleName()).append(":").append("\"" + field.asType().toString() + "\"");
if (i < fields.size() - 1) {
fw.append(",");
fw.append("\n");
}
}
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面生成文件的路径在mac写的是:/Users/xxx/Desktop/json/,xxx为你的用户名,这个地址自己看下。如果在windows上,比如生成在在d盘的json文件夹下可以写:d://json
4、在main下新建resources文件夹,在resources下新建META-INF文件夹,在META-INF下新建services文件夹,在services下新建文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中写如文件注解器全限定名
com.example.bill.txtlib.TxtProcessor
5、编译. 如果配置好gradle环境变量,在Java Module根目录下,即txtlib下(在项目AnnotationPractice目录下也可以,但是会全量编译项目)使用 gradle build 命令编译,编译成功后会在build下生成jar包(AnnotationPractice/txtlib/build/libs/txtlib.jar).
AS:Build->Clear Project->Make Project (全量编译)
6、在app中使用
- 将编译好的txtlib.jar拷贝到app的libs下,在app的build.gradle下dependencies中引入jar包就可以了
implementation files('libs/txtlib.jar')
- 在app中新建两个类,使用注解
@Txtization
public class People {
private int id;
String name;
protected int age;
}
public class Child {
@Txtization
private int cId;
@Txtization
String cName;
int age;
}
7、编译. 在app目录下使用 gradle build 命令编译,编译成功后会在桌面上生成一个json文件夹,就是我们生成的,里面有两个txt文件.
生成的内容如下:
com_bill_annotationpractice_Child.txt
class:"com.bill.annotationpractice.Child",
cId:"int",
cName:"java.lang.String"
com_bill_annotationpractice_People.txt
class:"com.bill.annotationpractice.People",
id:"int",
name:"java.lang.String",
age:"int"
注:我在第7步编译会出现错误,如果遇到这个错误,根据下面截图中错误提示有两个解决方案
- 在app的build.gradle下加入下面代码,不过提示也说了这个选项已经被弃用了,以后会删除.
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath = true
}
}
}
}
- 在app的build.gradle的dependencies中通过annotationProcessor引入jar
annotationProcessor files('libs/txtlib.jar')