自己動(dòng)手寫注解框架

前言

剛接觸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è)元注解,分別是:

  1. @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             
    
  2. @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 
    
  3. @Documented: 表示注解可以出現(xiàn)在javadoc中。

  4. @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)大家指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容