不忘初心 砥砺前行, Tomorrow Is Another Day !java
本文概要:android
注解分类:标准注解、元注解.安全
简称注解的注解,从而建立新的注解.bash
这里主要介绍@Target和@Retention两个元注解.jvm
修饰类型从枚举类ElementType取值ide
public enum ElementType {
/** 类,接口(包含注解类型)*/
TYPE,
/** 成员变量*/
FIELD,
/** 方法*/
METHOD,
/** 方法参数或构造方法参数 */
PARAMETER,
/** 构造方法 */
CONSTRUCTOR,
/** 局部变量 */
LOCAL_VARIABLE,
/** 注解类型,可参考元注解Retention声明 */
ANNOTATION_TYPE,
/** 包 */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
复制代码
保留策略从枚举类RetentionPolicy中取值工具
public enum RetentionPolicy {
/**
* 源码级java文件
*/
SOURCE,
/**
* 编译时class文件 This is the default behavior.
*/
CLASS,
/**
*
* 运行时,加载到jvm虚拟机中.
* 经过反射获取该注解信息
*/
RUNTIME
}
复制代码
//定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//运行时注解
public @interface Name {
//定义成员变量,能够设置默认值
String value() default "我是默认名";
}
//使用注解
public class UseAnnotation {
@Name(value = "小学僧")
public String getName() {
return "什么都没有";
}
@Name
public String getFinalName() {
return "什么都没有";
}
}
//解析注解
public class Main {
public static void main(String[] args) {
//经过反射处理
Method[] methods = UseAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
Name name = method.getAnnotation(Name.class);
System.out.println("注解的值:"+name.value());
}
}
}
//输出结果
注解的值:小学僧
注解的值:我是默认名
复制代码
定义了一个注解,而且分别在getName与getFinalName方法上使用;因为第二个方法上没有设置value,因此在反射调用时输出的是默认名.post
了解编译时注解,须要先了解下Element相关的知识.接下来看Element.学习
Element位于javax.lang.model.element包下,一个Element表明一个程序元素.
对应关系以下:gradle
package annotationDemo.compile; //PackageElement包
public class TestElement {//TypeElement类
private int value;//VariableElement变量
public int getValue() {//ExecutableElement方法
return value;
}
}
复制代码
Element的中重要API;
getEnclosingElement : 获取一个元素的外部元素.
getEnclosedElement : 获取一个元素的内部元素.
建立一个java-library,命名为:annotations
定义两个注解用来注入int和String类型数据.
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectInt {
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface injectString {
}
复制代码
再建立一个java-library,命名为:compiler
定义注解处理器,解析注解
//build.gradle文件
dependencies {
implementation project(':annotations')
implementation 'com.squareup:javapoet:1.11.1'//java文件生成工具
implementation 'com.google.auto.service:auto-service:1.0-rc3'
//注册注解处理,自动在ressources生成META-INF/services/javax.annotation.processing.Processor的文件.文件内容为:自定义的注解处理器的类的全路径.
}
复制代码
@SupportedAnnotationTypes({"com.cjy.lhk_annotations.mylhk,injectInt",
"com.cjy.lhk_annotations.mylhk.injectString"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class injectProcessor extends AbstractProcessor {
private static final ClassName CONTEXT =
ClassName.get("android.content", "Context");
//待生成java文件的的集合,key为被注解的类的类名,value为GenerateJavaFile对象
private HashMap<String, GenerateJavaFile> mGenerateJavaFiles = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (TypeElement typeElement : set) {//遍历全部注解类对应的TypeElement
//获取注解类对应被注解的对应元素
//好比注解被用在某个成员变量上,那么这个就是获取成员变量对应的元素
for (Element element : roundEnvironment.getElementsAnnotatedWith(typeElement)) {
addElementToGenerateJavaFile(element);
}
}
createJavaFile();
return true;
}
/**
* 解析Element,并添加一个注解元素到对应的GenerateJavaFile对象中
* (收集信息)
*
* @param element 注解元素
*/
private void addElementToGenerateJavaFile(Element element) {
//获取element对应成员变量所在的类,即被注解的类
TypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取外部元素
//System.out.println("---getQualifiedName =---" + typeElement.getQualifiedName());
String[] split = typeElement.getQualifiedName().toString().split("\\.");
String className = split[split.length - 1];
//经过父类的processingEnv获取报信者,用于在编译过程当中打印log
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "add element to generate file " + className);
//获取被注解类对应的GenerateJavaFile对象,若是没有,则建立
GenerateJavaFile generateJavaFile = mGenerateJavaFiles.get(className);
if (generateJavaFile == null) {
GenerateJavaFile file = new GenerateJavaFile();
//设置待生成java文件的包名
file.packageName = processingEnv.getElementUtils().getPackageOf(element).toString();
//设置待生成java文件的类名
file.className = className + "_Inject";
//初始化元素集合
file.elements = new ArrayList<>();
file.elements.add(element);
//保存被注解类所对应要生成java类的GenerateJavaFile对象
mGenerateJavaFiles.put(className, file);
} else {
//将注解元素添加到有的generateJavaFile对象中
generateJavaFile.elements.add(element);
}
}
/**
* 生成java文件
*/
private void createJavaFile() {
//遍历GenerateJavaFile集合
for (String className : mGenerateJavaFiles.keySet()) {
//获取一个GenerateJavaFile对象
GenerateJavaFile file = mGenerateJavaFiles.get(className);
//构建一个构造方法,该构造方法带有一个Context类型的参数
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(CONTEXT, "context");
//遍历该类中须要处理的注解元素
for (Element element : file.elements) {
//若是注解的成员变量是一个int类型
if (element.asType().toString().equals("int")) {
//在构造方法中添加一条语句
//例如:((MainActivity)context).one = context.getResources().getInteger(R.integer.one);
builder.addStatement("(($N)context).$N = context.getResources().getInteger(R.integer.$N)",
file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
//若是注解的是一个String类型
} else if (element.asType().toString().equals("java.lang.String")) {
//在构造方法中添加一条语句
//例如:((MainActivity)context).hello = context.getResources().getString(R.string.hello);
builder.addStatement("(($N)context).$N = context.getResources().getString(R.string.$N)",
file.className.split("_")[0], element.getSimpleName(), element.getSimpleName());
}
}
//构建一个类,添加一个上述的构造方法
TypeSpec typeSpec = TypeSpec.classBuilder(file.className)
.addModifiers(Modifier.PUBLIC)
.addMethod(builder.build())
.build();
try {
//输出java文件
JavaFile javaFile = JavaFile.builder(file.packageName, typeSpec).build();
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 待生成的Java文件信息
*/
private static class GenerateJavaFile {
String packageName;//包名
String className;//类名
List<Element> elements;//程序元素集合
}
}
复制代码
注解处理器大体工做流程:
- 收集信息
- 遍历全部注解类对应的TypeElement
- 经过TypeElement获取被注解的元素(如类,成员变量,方法对应的元素对象)
- 解析Element(被注解对应元素)信息,将被相关信息添加到待生成的文件对象中
- 生成java文件
对于compiler项目,咱们只需在编译时使用,运行时无需加载到jvm虚拟机中.因此采用apt的替代品annotationProcessor进行引入.
//android项目
dependencies {
implementation project(':annotations')
//对于android项目,因为插件的默认支持能够直接使用此方式,无需应用apt插件
annotationProcessor (:'compiler')
}
复制代码
因为本人技术有限,若有错误的地方,麻烦你们给我提出来,本人不胜感激,你们一块儿学习进步.