有时候转过头看回一些基础知识,才发现原来当时候本身以为很难的东西都是从基础知识衍生而来的,忽然会有点豁然开朗的感受。譬如说咱们今天要讲的知识点———注解。java
从Java1.5就开始引入,在注解中,咱们很容易就看到了Java的理念,"Write Once,Run Anywhere"。平时开发的时候咱们看到最多的注解莫过因而Java三种内建注解之一的@Override。android
@Override——当咱们想要复写父类中的方法时,咱们须要使用该注解去告知编译器咱们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。git
其实在Android开发中,咱们在不少第三方库中会常常看到注解,下面我就列举介绍一下在《Android高级进阶》中看到的一些关于运用到注解的例子:github
Java API中默认定义的注解咱们称之为标准注解,他们定义在java.lang,java.lang.annotation和javax.annotation包中,按照不一样场景分为三类:数据库
Support Annotation Library是从Android Support Library19.1开始引入的一个全新的函数包,它包含了一系列有用的元注解,用来帮助开发者在编译期间发现可能存在的Bug。api
如Butterknife、Dagger二、DBFlow、Retrofit、JUnit数组
以上都是总结了大部分在《Android高级进阶》中出现的注解的地方,不少注解没一个个解释,有兴趣的同窗能够本身去搜索一下本身想知道的注解的具体用途。咱们能够看到注解不管在java和Android中都是使用很普遍的,并且慢慢变得必不可少。下面咱们就进入咱们的主题,分别用两种方式去自定义注解。bash
那么咱们先列出一个简单的题目,而后用两种不一样的方式去实现:框架
运行时注解通常和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简单,因此咱们先来用这个去实现。dom
/* 用来指明注解的访问范围
* 1.源码级注解SOURCE,该类型的注解信息会留在.java源码中,
* 源码编译后,注解信息会被丢弃,不会保留在编译好的.class文件中;
* 2.编译时注解CLASS,注解信息会保留在.java源码里和.class文件中,
* 在执行的时候,会被Java虚拟机丢弃不回家再到虚拟机中;
* 3.运行时注解RUNTIME,java源码里,.class文件中和Java虚拟机在运行期也保留注解信息,
* 可经过反射读取
*/
@Retention(RUNTIME)
//是一个ElementType类型的数组,用来指定注解所使用的对象范围
@Target(value = FIELD)
public @interface Add {
float ele1() default 0f;
float ele2() default 0f;
}
复制代码
能够看到,由于是运行时注解,因此咱们定义了@Retention是Runtime,定义了ele1,ele2两个看上去像函数的变量(在注解里这样写算是变量而不是方法或函数)
public class InjectorProcessor {
public void process(final Object object) {
Class class1 = object.getClass();
//找到类里全部变量Field
Field[] fields = class1.getDeclaredFields();
//遍历Field数组
for(Field field:fields){
//找到相应的拥有Add注解的Field
Add addMethod = field.getAnnotation(Add.class);
if (addMethod != null){
if(object instanceof Activity){
//获取注解中ele1和ele2两个数字,而后把他们相加
double d = addMethod.ele1() + addMethod.ele2();
try {
//把相加结果的值赋给该Field
field.setDouble(object,d);
}catch (Exception e){
}
}
}
}
}
}
复制代码
就这样,咱们利用了反射,告诉了Add这个注解,在代码里找到你的时候,你该作什么,把工做作好,你就有饭吃。
3.使用
很快,咱们就用第一种方式实现了给出的题目;确实,在代码量上这种方式比较简单粗暴,可是这种方式并不经常使用。
有不经常使用的方式,确定就有经常使用的方式,下面咱们就来介绍这个经常使用的方式——注解处理器
著名的第三方框架ButterKnife也就是用这种方式去实现注解绑定控件的功能的。
注解处理器是(Annotation Processor)是javac的一个工具,用来在编译时扫描和编译和处理注解(Annotation)。你能够本身定义注解和注解处理器去搞一些事情。一个注解处理器它以Java代码或者(编译过的字节码)做为输入,生成文件(一般是java文件)。这些生成的java文件不能修改,而且会同其手动编写的java代码同样会被javac编译。看到这里加上以前理解,应该明白大概的过程了,就是把标记了注解的类,变量等做为输入内容,通过注解处理器处理,生成想要生成的java代码。
咱们能够看到全部注解都会在编译的时候就把代码生成,并且高效、避免在运行期大量使用反射,不会对性能形成损耗。 下面咱们就看看怎么去实现一个注解处理器:
为何咱们要新建这么多module呢,缘由很简单,由于有些库在编译时起做用,有些在运行时起做用,把他们放在同一个module下会报错,因此咱们秉着各司其职的理念把他们都分开了。
在写注解处理器以前咱们必须在lib_compiler中引入两个库辅助咱们成就大业:
咱们先在lib_compiler中建立一个基类
public class AnnotatedClass {
public Element mClassElement;
/**
* 元素相关的辅助类
*/
public Elements mElementUtils;
public TypeMirror elementType;
public Name elementName;
//加法的两个值
private float value1;
private float value2;
public AnnotatedClass(Element classElement) {
this.mClassElement = classElement;
this.elementType = classElement.asType();
this.elementName = classElement.getSimpleName();
value1 = mClassElement.getAnnotation(Add.class).ele1();
value2 = mClassElement.getAnnotation(Add.class).ele2();
}
Name getElementName() {
return elementName;
}
TypeMirror getElementType(){
return elementType;
}
Float getTotal(){
return (value1 + value2);
}
/**
* 包名
*/
public String getPackageName(TypeElement type) {
return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}
/**
* 类名
*/
private static String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}
}
复制代码
而后咱们的主角就要出场了——注解处理器 咱们建立一个文件,而后继承AbstractProcessor
@AutoService(Processor.class)
public class AddProcessor extends AbstractProcessor{
private static final String ADD_SUFFIX = "_Add";
private static final String TARGET_STATEMENT_FORMAT = "target.%1$s = %2$s";
private static final String CONST_PARAM_TARGET_NAME = "target";
private static final char CHAR_DOT = '.';
private Messager messager;
private Types typesUtil;
private Elements elementsUtil;
private Filer filer;
/**
* 解析的目标注解集合,一个类里能够包含多个注解,因此是Map<String, List<AnnotatedClass>>
*/
Map<String, List<AnnotatedClass>> annotatedElementMap = new LinkedHashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnv.getMessager();
typesUtil = processingEnv.getTypeUtils();
elementsUtil = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(Add.class.getCanonicalName());
return annotataions;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
由于该方法可能会执行屡次,因此每次进来必须clear
annotatedElementMap.clear();
//1.遍历每一个有Add注解的Element,
//2.而后把它加入Map里面,一个类里能够包含多个注解,因此是Map<String, List<AnnotatedClass>>,
//3.赋予它工做任务,告诉他你该作什么,
//4.而后生成Java文件
for (Element element : roundEnv.getElementsAnnotatedWith(Add.class)) {
//判断被注解的类型是否符合要求
if (element.getKind() != ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.ERROR, "Only FIELD can be annotated with @%s");
}
TypeElement encloseElement = (TypeElement) element.getEnclosingElement();
String fullClassName = encloseElement.getQualifiedName().toString();
AnnotatedClass annotatedClass = new AnnotatedClass(element);
//把类名和该类里面的全部关于Add注解的注解放到Map里面
if(annotatedElementMap.get(fullClassName) == null){
annotatedElementMap.put(fullClassName, new ArrayList<AnnotatedClass>());
}
annotatedElementMap.get(fullClassName).add(annotatedClass);
}
//由于该方法会执行屡次,因此size=0时返回true结束
if (annotatedElementMap.size() == 0) {
return true;
}
//用javapoet生成类文件
try {
for (Map.Entry<String, List<AnnotatedClass>> entry : annotatedElementMap.entrySet()) {
MethodSpec constructor = createConstructor(entry.getValue());
TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);
JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();
javaFile.writeTo(filer);
}
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Error on creating java file");
}
return true;
}
//如下是javapoet建立各类方法的实现方式
private MethodSpec createConstructor(List<AnnotatedClass> randomElements) {
AnnotatedClass firstElement = randomElements.get(0);
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(firstElement.mClassElement.getEnclosingElement().asType()), CONST_PARAM_TARGET_NAME);
for (int i = 0; i < randomElements.size(); i++) {
addStatement(builder, randomElements.get(i));
}
return builder.build();
}
private void addStatement(MethodSpec.Builder builder, AnnotatedClass randomElement) {
builder.addStatement(String.format(
TARGET_STATEMENT_FORMAT,
randomElement.getElementName().toString(),
randomElement.getTotal())
);
}
private TypeSpec createClass(String className, MethodSpec constructor) {
return TypeSpec.classBuilder(className + ADD_SUFFIX)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(constructor)
.build();
}
private String getPackage(String qualifier) {
return qualifier.substring(0, qualifier.lastIndexOf(CHAR_DOT));
}
private String getClassName(String qualifier) {
return qualifier.substring(qualifier.lastIndexOf(CHAR_DOT) + 1);
}
}
复制代码
咱们能够看到注解处理器总共有四个方法,他们分别的做用是:
init() 可选 在该方法中能够获取到processingEnvironment对象,借由该对象能够获取到生成代码的文件对象, debug输出对象,以及一些相关工具类
getSupportedSourceVersion() 返回所支持的java版本,通常返回当前所支持的最新java版本便可
getSupportedAnnotationTypes() 你所须要处理的全部注解,该方法的返回值会被process()方法所接收
process() 必须实现 扫描全部被注解的元素,并做处理,最后生成文件。该方法的返回值为boolean类型,若返回true,则表明本次处理的注解已经都被处理,不但愿下一个注解处理器继续处理,不然下一个注解处理器会继续处理。
好了,打了这么多代码,咱们先看下编译时生成的代码和文件是怎么样的,就会使用了:
看到了生成的AnnotationActivity_Add的文件,咱们下面就去写一个注入方法,把咱们想要结果拿出来展现:
咱们成功的用两种不一样的注解方式实现了两数相加的运算,1.运用的是反射,2.运用的是注解处理器。虽然看上去注解处理器的方式比较繁琐,可是使用比较广泛,并且有不少好处,这里就不一一述说。若是有兴趣学习的同窗能够下载源码去学习一下,互相交流,共同窗习。源码下载连接
参考文章:
更多文章: 个人简书