前言
剛接觸Java的時(shí)候就覺得注解是非常神奇,加之現(xiàn)在越來(lái)越多的開源項(xiàng)目采用注解的方式來(lái)實(shí)現(xiàn),如Dagger2,ButterKnife。因此在空余時(shí)間好好研究了一下,本文將向你介紹一些自定義注解所需要的基礎(chǔ)知識(shí)以及一個(gè)簡(jiǎn)單的例子。
基礎(chǔ)知識(shí)
元注解
所謂的元注解就是注解的注解。Java提供了4個(gè)元注解,分別是:
-
@Target:用于描述注解的使用范圍,如果自定義注解不存在@Target,則表示該注解可以使用在任何程序元素之上。接收參數(shù)ElementType,其值如下:/**接口、類、枚舉、注解**/ ElementType.TYPE /**字段、枚舉的常量**/ ElementType.FIELD /**方法**/ ElementType.METHOD /**方法參數(shù)**/ ElementType.PARAMETER /**構(gòu)造方法**/ ElementType.CONSTRUCTOR /**局部變量**/ ElementType.LOCAL_VARIABLE /**注解**/ ElementType.ANNOTATION_TYPE /**包**/ ElementType.PACKAGE /**表示該注解能寫在類型變量的聲明語(yǔ)句中。 java8新增**/ ElementType.TYPE_PARAMETER /**表示該注解能寫在使用類型的任何語(yǔ)句中。 java8新增**/ ElementType.TYPE_USE -
@Retention:表示注解類型保留的時(shí)長(zhǎng),它接收RetentonPolicy參數(shù),其值如下:/**注解僅存在于源碼中,在編譯階段丟棄。這些注解在編譯結(jié)束之后就不再有任何意義,所以它們不會(huì)寫入字節(jié)碼。**/ RetentionPolicy.SOURCE /**默認(rèn)的保留策略,注解會(huì)在class字節(jié)碼文件中存在,但運(yùn)行時(shí)無(wú)法獲得。**/ RetentionPolicy.CLASS /**注解會(huì)在class字節(jié)碼文件中存在,在運(yùn)行時(shí)可以通過反射獲取到。**/ RetemtionPolicy.RUNTIME @Documented: 表示注解可以出現(xiàn)在javadoc中。@Inherited:表示注解可以被子類繼承。
Annotation Processor Tool
Annotation Processor Tool是用于編譯期掃描和處理注解的工具,目前被集成在javac中。在編譯的時(shí)候,javac通常會(huì)找到你定義的注解處理器,并執(zhí)行注解處理。
不過遺憾的是,Android Studio默認(rèn)是不支持注解處理器的,我們需要引入一個(gè)額外的Gradle插件,android-apt,這個(gè)插件功能是:允許配置只在編譯時(shí)作為注解處理器的依賴,而不添加到最后的APK或library;設(shè)置源路徑,使注解處理器生成的代碼能被Android Studio正確的引用。
AbstractProcessor
AbstractProcessor 是 javac 掃描和處理注解的關(guān)鍵類,所有自定義的Processor都是繼承自AbastractProcessor,一個(gè)基本的Procssor結(jié)構(gòu)如下所示:
public class SimpleProcessor extends AbstractProcessor {
/**
* 每一個(gè)注解處理器類都必須有一個(gè)無(wú)參構(gòu)造方法。
* init方法是在Processor創(chuàng)建時(shí)被javac調(diào)用并執(zhí)行初始化操作。
* @param processingEnv 提供一系列的注解處理工具。
**/
@Override
public synchronized void init(ProcessingEnvironment env){ }
/**
* 注解處理需要執(zhí)行一次或者多次。每次執(zhí)行時(shí),處理器方法被調(diào)用,并且傳入了當(dāng)前要處理的注解類型。
* 可以在這個(gè)方法中掃描和處理注解,并生成Java代碼。
* @param annotations 當(dāng)前要處理的注解類型
* @param roundEnv 這個(gè)對(duì)象提供當(dāng)前或者上一次注解處理中被注解標(biāo)注的源文件元素。(獲得所有被標(biāo)注的元 素)
*/
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
/** 注解處理器要處理的注解類型,值為完全限定名(就是帶所在包名和路徑的類全名) **/
@Override
public Set<String> getSupportedAnnotationTypes() { }
/** 指定支持的 java 版本,通常返回 SourceVersion.latestSupported() **/
@Override
public SourceVersion getSupportedSourceVersion() { }
}
有一點(diǎn)需要注意,Android Library中去除了javax包的部分功能,所以,在新建Module的時(shí)候不能選Android Library,需要使用Java Library。
注冊(cè)Processor
想要讓javac執(zhí)行期間調(diào)用我們自定義的Processor,我們需要注冊(cè)自定義的Processor:
方法一:在main文件夾下創(chuàng)建resources/META-INF/javax.annotation.processing.Processor,在該文件中的內(nèi)容是以換行符分隔的Processor的完成限定類名(帶包名的):
me.tiny.autobuilder.AutoBuilderProcessor
me.tiny.other.OtherProcessor
方法二:使用Google提供的@AutoSerivce注解:
引入依賴:
dependencies {
compile 'com.google.auto.service:auto-service:1.0-rc2'
}
使用@AutoService生成META-INF/services/javax.annotation.processing.Processor文件:
AutoService(Processor.class)
public class AutoBuilderProcessor extends AbstractProcessor {
...
}
實(shí)戰(zhàn):AutoBuilder
ok,理論知識(shí)差不多介紹完畢,下面讓我們直接進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)。
在這里我簡(jiǎn)單介紹一下AutoBuilder這個(gè)項(xiàng)目的結(jié)構(gòu),該項(xiàng)目主要分為兩個(gè)Module,一個(gè)是library,另一個(gè)是compiler。library主要是放置所有自定義的注解類。而compiler則用于處理注解、生成相應(yīng)代碼。
library module
附上library工程目錄結(jié)構(gòu):
library
└── src
└── main
└── java
└── me
└── tiny
└── annotation
├── AutoBuilder.java
└── Ignore.java
可以看到library非常簡(jiǎn)單,只定義了兩個(gè)注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface AutoBuilder {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Ignore {
}
compiler module
附上compiler工程目錄結(jié)構(gòu):
compiler
├── build
│ ├── classes
│ └── main
│ ├── META-INF
│ └── services
│ └── javax.annotation.processing.Processor
└── src
└── main
└── java
└── me
└── tiny
└── autobuilder
├── AutoBuilderProcessor.java
├── CodeGenerator.java
├── CodeGeneratorHelper.java
├── exceptions
│ ├── AbstractClassRejectedException.java
│ ├── ConstructorRejectedException.java
│ └── RuleRejectedException.java
└── rules
├── AbstractClassRejectRule.java
├── ConstructorRejectRule.java
└── Rule.java
在創(chuàng)建complier module時(shí)需要注意使用Java library,并且在項(xiàng)目頂層的build.gradle文件中添加android-apt插件依賴,具體代碼如下:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
然后我們?cè)赾ompiler的builder.gradle中添加library和google auto service的依賴:
dependencies {
...
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile project(":library")
}
AutoBuilderProcessor
之前說(shuō)過,自定義的Processor是javac掃描和處理注解的關(guān)鍵類,讓我們來(lái)看一下我們的處理器類:
@AutoService(Processor.class)
public class AutoBuilderProcessor extends AbstractProcessor {
private List<Rule> mRules;
private Messager mErrorMessager;
/**
* 每一個(gè)注解處理器類都必須有一個(gè)無(wú)參構(gòu)造方法。
* init方法是在Processor創(chuàng)建時(shí)被apt調(diào)用并執(zhí)行初始化操作。
* @param processingEnv 提供一系列的注解處理工具。
**/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mRules = new ArrayList<>();
mRules.add(new AbstractClassRejectRule());
mRules.add(new ConstructorRejectRule());
mErrorMessager = processingEnv.getMessager();
}
/**
* 注解處理需要執(zhí)行一次或者多次。每次執(zhí)行時(shí),處理器方法被調(diào)用,并且傳入了當(dāng)前要處理的注解類型。
* 可以在這個(gè)方法中掃描和處理注解,并生成Java代碼。
* @param annotations 當(dāng)前要處理的注解類型
* @param roundEnv 這個(gè)對(duì)象提供當(dāng)前或者上一次注解處理中被注解標(biāo)注的源文件元素。(獲得所有被標(biāo)注的元素)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(AutoBuilder.class)) {
//判斷當(dāng)前Element是否是類。
//不用 annotatedElement instanceof TypeElement的原因是interface也是TypeElement.
if (annotatedElement.getKind() == ElementKind.CLASS) {
TypeElement annotatedClass = (TypeElement) annotatedElement;
try {
validateRule(annotatedClass);
} catch (RuleRejectedException e) {
mErrorMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
return true;
}
generateCode(annotatedClass);
}
}
return true;
}
private void generateCode(TypeElement annotatedClass) {
//獲取包名
String packageName = processingEnv.getElementUtils()
.getPackageOf(annotatedClass).getQualifiedName().toString();
CodeGenerator codeGenerator = new CodeGenerator(packageName, annotatedClass);
try {
codeGenerator.generateJavaFile(processingEnv.getFiler());
} catch (IOException e) {
mErrorMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
}
/**
* @return 返回支持的Annotation類型
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add(Ignore.class.getCanonicalName());
supportedAnnotationTypes.add(AutoBuilder.class.getCanonicalName());
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@SuppressWarnings("unchecked")
private void validateRule(TypeElement element) throws RuleRejectedException {
for (Rule rule : mRules) {
rule.validateRule(element);
}
}
}
init
在init方法中我們通過super.init(processingEnv)方法得到了processingEnv的引用。通過processingEnv對(duì)象我們能獲得如下引用:
- Elements:一個(gè)處理Element的的工具類。
- Types:一個(gè)處理TypeMirror的工具類。
- Filer:定義了一些關(guān)于創(chuàng)建源文件,類文件和一般資源的方法。
- Messager:提供給注解處理器一個(gè)報(bào)告錯(cuò)誤、警告以及提示信息的途徑,它不是注解處理器開發(fā)者的日志工具,而是用來(lái)寫一些信息給使用此注解器的第三方開發(fā)者的。
process
首先,需要說(shuō)明一下Element的含義,Element代表程序的元素,例如包、類、方法、成員變量。對(duì)應(yīng)關(guān)系如下:
PackageElement ---> 包
ExecuteableElement ---> 方法、構(gòu)造方法
VariableElement ---> 成員變量、enum常量、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)。
TypeElement ---> 類、接口
TypeParameterElement ---> 在方法或構(gòu)造方法、類、接口處定義的泛型參數(shù)。
在process中我們通過RoundEnvironment對(duì)象的getElementsAnnotatedWith方法獲得所有包含@AutoBuilder注解的Element的集合。接下來(lái),我們必須檢查這些Element是否是一個(gè)類:
...
//判斷當(dāng)前Element是否是類。
//不用 annotatedElement instanceof TypeElement的原因是interface也是TypeElement.
if (annotatedElement.getKind() == ElementKind.CLASS) {
...
}
...
然后,我們需要校驗(yàn)該類是否滿足生成Builder類的規(guī)則,規(guī)則如下:
/**
* 校驗(yàn)是否是抽象方法
*/
public class AbstractClassRejectRule implements Rule<TypeElement> {
@Override
public void validateRule(TypeElement element) throws RuleRejectedException {
if (element.getModifiers().contains(Modifier.ABSTRACT)) {
throw throwException(element);
}
}
@Override
public RuleRejectedException throwException(TypeElement element) {
return new AbstractClassRejectedException(
String.format("The class %s is abstract. You can't annotate abstract classes with @%s",
element.getQualifiedName().toString(), AutoBuilder.class.getSimpleName()));
}
}
/**
* 用于判斷是否提供非private的無(wú)參構(gòu)造函數(shù)
*/
public class ConstructorRejectRule implements Rule<TypeElement> {
@Override
public void validateRule(TypeElement element) throws RuleRejectedException {
for (ExecutableElement executableElement : ElementFilter.constructorsIn(element.getEnclosedElements())) {
if (!executableElement.getModifiers().contains(Modifier.PRIVATE)
&& executableElement.getParameters().isEmpty()) {
return;
}
}
throw throwException(element);
}
@Override
public RuleRejectedException throwException(TypeElement element) {
return new ConstructorRejectedException(
String.format("The class %s must provide an non-private empty constructor", element.getQualifiedName().toString()));
}
}
如果該TypeElement不滿足規(guī)則,會(huì)拋出一個(gè)錯(cuò)誤,我們需要在process中捕獲錯(cuò)誤并通過processingEnv提供的Messager類將錯(cuò)誤信息發(fā)送給第三方開發(fā)者,方便他們找到錯(cuò)誤原因。
還有一點(diǎn)需要注意的是process方法可能會(huì)被多次執(zhí)行,當(dāng)我們生成新的源文件時(shí),它就會(huì)被再次執(zhí)行(只有一次init,process會(huì)執(zhí)行多次)。如果重新創(chuàng)建已經(jīng)生成的源代碼,將會(huì)拋出一個(gè)異常。
代碼生成
使用Square公司出品的JavaPoet來(lái)生成java源代碼。
public class CodeGenerator {
private final String mPackageName;
private final AnnotatedClass mAnnotatedClass;
private final ClassName mAnnotatedClassName;
private final ClassName mGeneratedClassName;
private final static String SUFFIX = "Builder";
public CodeGenerator(String packageName, TypeElement typeElement) {
mPackageName = packageName;
mAnnotatedClass = new AnnotatedClass(typeElement);
mAnnotatedClassName = ClassName.get(packageName, typeElement.getSimpleName().toString());
mGeneratedClassName = ClassName.get(packageName, typeElement.getSimpleName().toString() + SUFFIX);
}
private TypeSpec generateCode() {
TypeSpec.Builder builder = TypeSpec.classBuilder(mGeneratedClassName)
//添加修飾符
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(builder())
.addMethod(build());
for (Element field : mAnnotatedClass.getFields()) {
TypeName fieldClass = ClassName.get(field.asType());
String fieldName = field.getSimpleName().toString();
builder.addField(fieldClass, fieldName, Modifier.PRIVATE);
builder.addMethod(MethodSpec.methodBuilder(fieldName)
.addParameter(fieldClass, fieldName)
.addModifiers(Modifier.PUBLIC)
.returns(mGeneratedClassName)
.addStatement("this.$L = $L", fieldName, fieldName)
.addStatement("return this")
.build());
}
return builder.build();
}
public void generateJavaFile(Filer filer) throws IOException {
JavaFile javaFile = JavaFile.builder(mPackageName, generateCode()).build();
javaFile.writeTo(filer);
}
/**
* 創(chuàng)建build方法
* @return MethodSpec
*/
private MethodSpec build() {
MethodSpec.Builder builder = MethodSpec.methodBuilder("build")
.addModifiers(Modifier.PUBLIC)
.returns(mAnnotatedClassName)
.addStatement("$T var = new $T()", mAnnotatedClassName, mAnnotatedClassName);
for (Element field : mAnnotatedClass.getFields()) {
String fieldName = field.getSimpleName().toString();
builder.addStatement("var.$L = $L", fieldName, fieldName);
}
return builder.addStatement("return var").build();
}
/**
* 創(chuàng)建builder方法
* @return MethodSpec
*/
private MethodSpec builder() {
return MethodSpec.methodBuilder("builder")
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.returns(mGeneratedClassName)
.addStatement("return new $T()", mGeneratedClassName)
.build();
}
private static class AnnotatedClass {
private List<Element> mFields;
public AnnotatedClass(TypeElement typeElement) {
mFields = CodeGeneratorHelper.filterFields(typeElement);
}
public List<Element> getFields() {
return mFields;
}
}
}
public class CodeGeneratorHelper {
/**
* @param element TypeElement
* @return 過濾被@Ignore、static、final、private標(biāo)識(shí)的字段
*/
public static List<Element> filterFields(TypeElement element) {
List<Element> elements = new ArrayList<>();
for (Element builderField : ElementFilter.fieldsIn(element.getEnclosedElements())) {
boolean isIgnored = builderField.getAnnotation(Ignore.class) != null
|| builderField.getModifiers().contains(Modifier.STATIC)
|| builderField.getModifiers().contains(Modifier.FINAL)
|| builderField.getModifiers().contains(Modifier.PRIVATE);
if (!isIgnored) {
elements.add(builderField);
}
}
return elements;
}
}
使用AutoBuilder
想要使用AutoBuilder,我們需要在app的build.gradle中添加如下代碼(使用apply plugin: 'com.neenbedankt.android-apt'的前提是已經(jīng)在項(xiàng)目頂層的build.gradle中添加的android-apt的依賴):
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile project(":library")
apt project(':compiler')
}
@AutoBuilder
public class Person {
int age;
String name;
/**省略get、set方法**/
}
//生成的代碼如下:
public final class PersonBuilder {
private String name;
private int age;
public static PersonBuilder builder() {
return new PersonBuilder();
}
public Person build() {
Person var = new Person();
var.name = name;
var.age = age;
var.address = address;
return var;
}
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
}
結(jié)束語(yǔ)
好了,這里拋磚引玉,簡(jiǎn)單介紹了一下注解的處理流程,更進(jìn)一步的應(yīng)用大家可以查看其他注解框架的源碼。附上項(xiàng)目Github地址。
本人第一次寫博客,加之水平有限,有什么不對(duì)的地方還請(qǐng)大家指正。