前言
作为一个开发人员,看到@Override都很熟悉吧。很多主流框架也大量运用了注解,那么我们想实现自己的注解应该这么做呢?折腾了一大圈,算是掌握了注解的使用。最后发现对于自定义注解,尤其是对于初学者而言,代码怎么写不是最难的,难的是环境的搭建。
先灌个鸡汤,给你点动力,注解既能解放生产力,也能装装X。
本文目的
- 了解注解,入门。
- 在Android Studio上的使用。
注解是什么鬼?
java中Annotation是代码里的特殊标记,这些标记在编译,类加载,运行时被读取,并作出相应的处理。
自定义Annotation
1. 定义方法
定义一个Annotation需要使用@interface关键字,Annotation没有方法,只有成员变量,但是成员变量定义的方式和方法的定义很像。
public @interface PrintHello{
//看起来很像方法,但是是成员变量,只是其定义形式很像方法的定义
int value();
String what();
}
然后使用方法如下:
@PrintHello(value=0,what="")
public int a ;
2.元注解
仅仅用上面的方法是不够的,还需要用JDK提供的元注解来修饰我们的自定义注解。详细介绍如下图:
一个完整注解的定义如下:
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Target({ElementType.FIELD,ElementType.METHOD , ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface PrintHello {
int value();
String what();
}
3.注解信息的提取
这一步是重点啦,坑大多在这一步。
上面说了注解有三种保存策略,其中runtime是在运行时通过反射来实现的,说起反射,大家心里第一个念头肯定是影响效率。但是这并不能阻止我们使用强大的注解,因为还有编译时注解,将保存策略设置为source或者class级别的。
首先,我们创建一个提取类,这个类是继承自AbstractProcessor的。
代码如下:
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
/*
这两个注解分别对应我注释掉的getSupportedAnnotationTypes()和getSupportedSourceVersion,在java7以上可以用注解代替掉这两个方法。
*/
//注意:必须写全路径 com.wl.annotation.PrintHello
@SupportedAnnotationTypes({"com.wl.annotation.PrintHello"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ZeusAnnotation extends AbstractProcessor {
/* 支持的注解,可以使用通配符
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new HashSet<String>();
set.add("com.wl.annotation.PrintHello");
return set;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
File file = new File("D:\\javaFile\\test");
if(!file.exists())
file.mkdirs();
return true;
}
}
这段代码也没什么好说的,process()方法就是我们需要处理逻辑的地方,这里我创建了一个文件,通过是否创建了文件来判断注解是否运行了,因为输出语句是在编译器,在Eclipse中不知道怎么查看编译信息,Android Studio中就可以直接查看编译信息。
==向javac注册注解处理器==
当我们的注解处理器写好以后怎么用呢,要将它注册到javac中,这样编译的时候才找得到。
注意,是Eclipse环境,下面会详细介绍AS环境
在工程中创建与src同级的目录META-INF,然后在META-INF中创建文件夹services,然后在services文件夹中创建文件,文件名为javax.annotation.processing.Processor,然后编辑该文件,里面写上你的注解处理器的路径(含包名),比如我的是com.wl.annotation.ZeusAnnotation
看图:
将该工程导出为jar包,只勾选src和META-INF两个文件夹。
接着创建一个测试工程,将上一步导出的jar包拷贝到libs文件夹中并添加到依赖工程中,接着在代码中使用注解。
public class Student implements Serializable{
@PrintHello(value = 0, what = "")
public int stuId;
public String stuName;
public boolean stuSex;
public int stuAge;
@PrintHello(value = 0, what = "")
public void setId(int id){
this.stuId = id;
}
@PrintHello(value = 0, what = "")
public Student(int stuId , String stuName , boolean stuSex , int stuAge){
this.stuId = stuId;
this.stuName = stuName;
this.stuSex = stuSex;
this.stuAge = stuAge;
}
@PrintHello(value = 0, what = "")
public Student() {
// TODO Auto-generated constructor stub
}
}
到这里你可能会问了,怎么没看到你向javac注册啊?别急,下面就是了。
工程右键 → properties → Java Compile
到这里就可以了,run一下我们的工程,可以看到D盘下生成了测试文件。
接下来就是高能时刻了,在AS上使用自定义注解,那可真是一把辛酸泪啊
古努尔哈赤有七大恨,今有注解几大坑啊,浪费了我一大堆时间。
- 合适的资料少,百度google了一大圈也没几个真正适合的。
- 因为在Eclipse中将jar包添加到factory path的时候只能添加jar包,所以有的博客就说注解只能用jar包,然后在AS下打包,关键在AS下还要用maven,对于android开发者来说真是一口老血吐出来了,三丈远的那种。
- 版本问题,可能以前Gradle不支持APT(Annotation Processor Tool),很多博客都提到了要用apt插件,也有人说Gradle2.2以后支持apt了,但是据我测试,我2.1.0的gradle是支持apt的。
- 就是关于META-INF文件夹的,我一开始是先从AS开始上手注解的,然后有的博客自己手动创建该目录,有的用auto-service插件,有的用maven打包,有的又不用,有的用apt插件,有的说不用,你可知道我的心情。然后我就先去从Eclipse入手,搞清楚了再来从AS搞起。
重点了,建议认真阅读,不要跳过
1. 在AS中可以以添加依赖的方法使用注解,不一定非要用jar包。
2. auto-service插件是自动帮助我们创建META-INF文件夹及里面的文件的,如果我们自己写就不需要用该插件。同时,不用即用这个插件又自己创建,否则会造成文件冲突。
3. 在AS中,要创建和java同级目录resources,里面创建文件夹META-INF,里面创建文件夹services,里面创建文件javax.annotation.processing.Processor
4. Gradle2.1.0以上是支持apt的,只是不是用apt而是用compile或者provided。
5. compile和provided的区别是前者添加的会被打包进apk,而后者添加的只是在编译器用,不会被打包进apk。
先看看工程结构:
其中:annotation里面是定义的注解,annotation-compile是注解提取类。
然后annotation-compile的build.gradle中添加annotation依赖。
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':annotation')
}
然后在app的build.gradle中添加依赖:
compile project(':annotation')
provided project(':annotation-compile')
好了,编译运行,在Gradle Console可以看到编译过程中输出了hello信息。同时反编译我们apk,可以看到源码里面没有提取器的代码。
后记
到这里终于大功告成了,走了很多弯路,但是最后掌握的感觉也很棒。在这个过程中看到很多博客,从前人那里学了很多同时也发现一些错误,在此希望这篇博客能让大家少踩一点坑。