前言
刚接触java不久的小猿们可能不知道注解这东东干嘛的,或者有些老鸟们也是一知半解只会用而不知其所以然。所以,在了解注解是什么之前,我先来说说这个东东有神马用,场景都有哪些,目前有没有范本。知道这些后,再看看,各位当前阶段是不是需要学,还是后续用到在深究。首先,说下用处,通过注解主要可以简化开发人员重复性工作和自动化过程,但不提高代码执行效率,用于框架的搭建与工具的开发;大部分应用场景包含在于编译阶段与运行时阶段自动生成代码;目前,用注解开发的框架有很多如:ButterKnife, EventBus, Retrofit,ARouter
等。嗯,闲扯了这么多,现在说下读完这篇文章你能知道些啥,先上个目录
- 注解的概念
- 元注解
- 自定义注解(注解的使用)
- 运行时注解(引擎 --> 反射)
- 编译时注解(核心 --> 注解解析器)
- 自动生成代码帮助库
JavaPoet
什么是注解
注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署
元注解
元注解就是注解的注解,哭,说啥勒。其实可以理解为类比于java提供的关键字,元注解目前分为4种如下(类比于java提供多个关键字)。每个元注解(关键字)怎么起作用的,那就是编译器的事咯。
1.@Retention:定义该注解的生命周期(被描述的注解在什么范围内有效)。参数是RetentionPolicy枚举对象
@Retention-RetentionPolicy类型 | 说明 |
---|---|
RetentionPolicy.SOURCE | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃 |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,用于编译时注解 |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在(用于运行时注解) |
2.@Target:表明我们注解可以出现的地方(没有说明包含所有),参数是一个ElementType类型的数组
@Target-ElementType类型 | 说明 |
---|---|
ElementType.TYPE | 接口、类、枚举、注解 |
ElementType.FIELD | 字段、枚举的常量 |
ElementType.METHOD | 方法 |
ElementType.PARAMETER | 方法参数 |
ElementType.CONSTRUCTOR | 构造函数 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
ElementType.PACKAGE | 包 |
3.@Document(用的少)
表明我们标记的注解可以被javadoc此类的工具文档化
4.@Inherited(用的少)
表明我们标记的注解是被继承的,默认是false。比如,如果一个父类使用了@Inherited修饰的注解,则允许子类继承该父类的注解。
自定义注解
自定义注解流程,so easy 如下:
- 申明注解
- 解析注解(两种模式如下)
- 反射方式的注解处理器(运行时注解)
- AbstractProcessor 的实现类注解处理器(编译时注解)
- 配置注解解析器
- 注解的使用
下面就通过AS来演示这几步骤,首先,创建Module1:annotation(选择为 Java library),该Module用于申明注解,比如:(ARouter用于模拟阿里的路由框架、Bindview用于模拟Butterknife 库)
/**
* 根据ID查看View的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FindViewById {
// 使用value命名,则使用的时候可以忽略,否则使用时就得把参数名加上
int value();
}
/**
* 设置点击事件
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SetOnClickListener {
int id();
String methodName();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
String path();
String group() default "";
}
步骤二:创建Module2:compiler(选择为 Java Library或者Android Library),该Module用于解释注解
注意:一般情况运行时注解选择Android Library,编译时注解使用Java Library(因为AbstractProcessor类依赖于rt.jar包)
反射方式的注解处理器(运行时注解)
import android.app.Activity;
import android.view.View;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Annotation解析器
*/
public class ButterKnifeProcess{
/**
* 解析
* @param target 解析目标
*/
public static void parse(final Activity target) {
try {
Class<?> clazz = target.getClass();
// 获取所有的字段
Field[] fields = clazz.getDeclaredFields();
FindViewById byId;
SetOnClickListener clkListener;
View view;
String name;
String methodName;
int id;
for (Field field : fields){
//返回该元素上存在的所有注解
Annotation[] annotations = field.getAnnotations();
for(Annotation annotation:annotations) {
if (annotation instanceof FindViewById) {
// 获取FindViewById对象(返回该元素上存在的指定类型的注解)
byId = field.getAnnotation(FindViewById.class);
// 反射访问私有成员,必须进行此操作
field.setAccessible(true);
//字段名
name = field.getName();
id = byId.value();
// 查找对象
view = target.findViewById(id);
if (view != null)
field.set(target, view);
else
throw new Exception("Cannot find.View name is " + name + ".");
} else if (annotation instanceof SetOnClickListener) { // 设置点击方法
//返回该元素上存在的指定类型的注解
clkListener = field.getAnnotation(SetOnClickListener.class);
field.setAccessible(true);
// 获取变量
id = clkListener.id();
methodName = clkListener.methodName();
name = field.getName();
view = (View) field.get(target);
if (view == null) { // 如果对象为空,则重新查找对象
view = target.findViewById(id);
if (view != null)
field.set(target, view);
else
throw new Exception("Cannot find.View name is " + name + ".");
}
// 返回当前类所有的方法,即包括public、private和protected,不包括父类
Method[] methods = clazz.getDeclaredMethods();
boolean isFind = false;
for (final Method method:methods) {
if (method.getName().equals(methodName)) {
isFind = true;
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
method.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
}
});
break;
}
}
// 没有找到
if (!isFind) {
throw new Exception("Cannot find.Method name is " + methodName + ".");
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
AbstractProcessor 的实现类注解处理器(编译时注解)
package com.jsonan.compile;
import com.google.auto.service.AutoService;
import com.jsonan.annotation.ARouter;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
/**
* AbstractProcessor 是一个抽象类,它实现了 Processor 接口。
* Processor 接口的实现类必须提供一个公共的无参数构造方法,虚拟机工具将使用该构造方法实例化 Processor。
* 虚拟机工具框架与实现此接口的类交互过程:
* 1.创建 Processor 对象
* 2.调用 Processor 对象 的 init 方法
* 3.调用 Processor 对象 的 getSupportedAnnotationTypes、getSupportedOptions 和 getSupportedSourceVersion 方法(下面我们使用注解的方式实现这几个方法)
* 4.调用 Processor 对象 的调用 process 方法
*/
@AutoService(Processor.class)
// 设置此注解解析器支持的注解类型的名称
@SupportedAnnotationTypes("com.jsonan.annotation.ARouter")
// 设置此注解解析器支持的最新的 JDK 版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 设置此注解解析器识别的选项
@SupportedOptions("content")
public class ARouterProcessor extends AbstractProcessor {
//一个用来处理Element元素的工具类
private Elements elementUtils;
//用来处理TypeMirror的工具类
private Types typeUtils;
//输出警告、错误日志工具类
private Messager messager;
//文件生成器
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
//该content内容的参数从使用注解的Module中的build.gradle里传递,如:
//android {
// defaultConfig {
// javaCompileOptions{
// annotationProcessorOptions {
// arguments = [content:'hello apt']
// }
// }
// }
String content = processingEnvironment.getOptions().get("content");
messager.printMessage(Diagnostic.Kind.NOTE, content);
}
/**
*注解Processor扫描出的结果会存储进roundEnvironment中,可以在这里获取到注解内容,编写你的操作逻辑
*注意:process()函数中不能直接进行异常抛出,否则程序会异常崩溃
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (set.isEmpty()) {
return false;
}
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) {
String packName = elementUtils.getPackageOf(element).getQualifiedName().toString();
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
String finalClassName = className + "$$ARouter";
try {
//此时会在app\build\generated\ap_generated_sources\debug\xxx\包下生成MainActivity$$ARouter.java文件
JavaFileObject sourceFile = filer.createSourceFile(packName + "." + finalClassName);
Writer writer = sourceFile.openWriter();
writer.write("package " + packName + ";\n");
writer.write("public class " + finalClassName + "{\n");
writer.write("public static Class<?> findTargetClass(String path){\n");
// 返回此元素上存在的指定类型的注解
ARouter aRouter = element.getAnnotation(ARouter.class);
writer.write("if (path.equalsIgnoreCase(\"" + aRouter.path() + "\")){\n");
writer.write("return " + className + ".class;\n}\n");
writer.write("return null;\n");
writer.write("}\n}\n");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
注解处理器的核心是process()方法,而process()方法的核心是Element元素。Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的Element。它可以代表包、类、接口、方法、字段等多种元素种类。所有Element肯定是有好几个子类。如下所示。
Element子类 | 解释 |
---|---|
TypeElement | 类或接口元素 |
VariableElement | 字段、enum常量、方法或构造方法参数、局部变量或异常参数元素 |
ExecutableElement | 类或接口的方法、构造方法,或者注解类型元素 |
PackageElement | 包元素 |
TypeParameterElement | 类、接口、方法或构造方法元素的泛型参数 |
关于Element类里面的方法我们也做一个简单的介绍:
/**
* 返回此元素定义的类型,int,long这些
*/
TypeMirror asType();
/**
* 返回此元素的种类:包、类、接口、方法、字段
*/
ElementKind getKind();
/**
* 返回此元素的修饰符:public、private、protected
*/
Set<Modifier> getModifiers();
/**
* 返回此元素的简单名称(类名)
*/
Name getSimpleName();
/**
* 返回封装此元素的最里层元素。
* 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素;
* 如果此元素是顶层类型,则返回它的包;
* 如果此元素是一个包,则返回 null;
* 如果此元素是一个泛型参数,则返回 null.
*/
Element getEnclosingElement();
/**
* 返回此元素直接封装的子元素
*/
List<? extends Element> getEnclosedElements();
/**
* 返回直接存在于此元素上的注解
* 要获得继承的注解,可使用 getAllAnnotationMirrors
*/
List<? extends AnnotationMirror> getAnnotationMirrors();
/**
* 返回此元素上存在的指定类型的注解
*/
<A extends Annotation> A getAnnotation(Class<A> var1);
Element的子类的使用举例,如下:
//VariableElement 修饰属性、类成员的注解
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
//ElementType.FIELD注解可以直接强转VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 属性、类成员的直接封装上层元素就是类,所以是TypeElement
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();
}
注意:上面process()函数中我们使用JavaFileObject
的方式生成java文件,如果java文件比较多,内容复杂时很容易出错,有个便捷的工具库供我们使用JavaPoet
,这个下面最后部分详说,暂且跳过先把整个流程说完
步骤三:
方式一:创建 javax.annotation.processing.Processor 文件:在 main 文件夹下,与 java 同一级目录中创建 resources文件夹,再创建 META-INF/services/javax.annotation.processing.Processor 文件,在javax.annotation.processing.Processor 文件中指明注解解析器类 Processor
的 类路径(处理器的全类名),如果有多个注解处理器换行写入就可以。
方式二:
-
在处理器Module:compile的build.gradle中添加google的自动生成服务库,如:
dependencies { implementation 'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' implementation project(path:':annotation') } sourceCompatibility = "7" targetCompatibility = "7"
-
在注解处理器类前面加上@AutoService(Processor.class),如:
@AutoService(Processor.class) public class ButterKnifeProcess{ ... }
方法三:如果第二步创建的Module是Android Library ,此时可以在build.gradle 文件的 dependencies 块中,添加 support-annotations 依赖项
dependencies { compile 'com.android.support:support-annotations:24.2.0' }
若该模块使用了appcompat库,则无需添加support-annotations依赖项。因为 appcompat库已经依赖注解库
步骤四:注解的使用,需要在使用它 Module 的 build.gradle 文件中添加依赖。比如在项目主 Module :app要使用,则在主 Module的 build.gradle 中添加依赖:
dependencies {
annotationProcessor project(path: ':compile(解析注解Module的名字)')
implementation project(path: ':annotation(申明注解Module的名字)')
}
如果编写的注解解析 Module 只是在编译时扫描,还可以将依赖修改为:
dependencies {
annotationProcessor project(path: ':compile(解析注解Module的名字)')
complileOnly project(path: ':annotation(申明注解Module的名字)')
}
编译时注解、运行时注解使用如下:
//运行时注解
@ARouter(path = "/app/MainActivity", group = "app")
public class MainActivity extends AppCompatActivity {
//自动绑定view(编译时注解)
@FindViewById(R.id.tv_Toast)
@SetOnClickListener(id = R.id.tv_Toast, methodName = "click")
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnifeProcess.parse(this);
}
/**
* 点击事件
*/
public void click() {
Toast.makeText(this, "Hello compile annotatioin", Toast.LENGTH_SHORT).show();
}
}
JavaPoet的简单介绍与使用
JavaPoet是一款使用编程的方式自动生成java文件的三方库,通常用编写模板类,框架等
直接上使用(对标上面使用Filer方式编译时生成的MainActivity$$ARouter.java文件),JavaPoet具体介绍参考下面的参考链接
使用JavaPoet需要生成的文件
package com.jsonan.annotationlearning;
import java.lang.Class;
import java.lang.String;
public class MainActivity$$ARouter {
public static Class<?> findTargetClass(String path) {
if (path.equalsIgnoreCase("/app/MainActivity")) {
return MainActivity.class;
}
return null;
}
}
JavaPoet使用如下:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
ClassName path = ClassName.get("java.lang", "String");
ParameterSpec savedInstanceState = ParameterSpec.builder(path, "path").build();
//构造一个方法
MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass") //名称
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) //修饰
.returns(Class.class) //返回
.addParameter(savedInstanceState) //参数
.beginControlFlow("if($N.equalsIgnoreCase(\"/app/MainActivity\"))",savedInstanceState)
.addStatement("return MainActivity.class") //语句
.endControlFlow()
.addStatement("return null") //语句
.build();
//构造一个类
TypeSpec MainActivity$$ARouter = TypeSpec.classBuilder("MainActivity$$ARouter") //名称
.addModifiers(Modifier.PUBLIC) //修饰
.addMethod(findTargetClass) //方法
.build();
JavaFile javaFile = JavaFile.builder("com.jsonan.annotationlearning",MainActivity$$ARouter).build();
try {
javaFile.writeTo(System.out);
} catch (IOException e) {
e.printStackTrace();
}
File file = new File("."+"\\src\\");
if (file.exists()) {
file.delete();
}
javaFile.writeTo(file);
return false;
}
参考链接
https://blog.csdn.net/wuyuxing24/article/details/81139846
https://blog.csdn.net/wangjiang_qianmo/article/details/99350360