Java注解解析-搭建自己的注解處理器(CLASS注解使用篇)

寫在前面

該文章講解了如何處理源碼注解(CLASS ),是繼Java注解解析-基礎(chǔ)+運(yùn)行時(shí)注解(RUNTIME)之,使用注解處理器處理CLASS注解的文章。通過(guò)完整的Demo例子介紹整個(gè)注解處理器的搭建流程以及注意事項(xiàng),你將知道如何去搭建自己的注解處理器。前提是你知道如何去寫自定義注解,不清楚的可以點(diǎn)擊我上面的鏈接哦~

介紹

顧名思義注解處理器就是javac包中專門用來(lái)處理注解的工具。所有的注解處理器都必須繼承抽象類AbstractProcessor然后重寫它的幾個(gè)方法。注解處理器是運(yùn)行在它自己的JVM中。javac啟動(dòng)一個(gè)完整Java虛擬機(jī)來(lái)運(yùn)行注解處理器,這意味著你可以使用任何你在其他java應(yīng)用中使用的的東西。其中抽象方法process是必須要重寫的,再該方法中注解處理器可以遍歷所有的源文件,然后通過(guò)RoundEnvironment類獲取我們需要處理的注解所標(biāo)注的所有的元素,這里的元素可以代表包,類,接口,方法,屬性等,具體的解釋放在下一章節(jié),因?yàn)檫@里面牽扯到的東西實(shí)在是太多了。再處理的過(guò)程中可以利用特定的工具類自動(dòng)生成特定的.java文件或者.class文件,來(lái)幫助我們處理自定義注解。

下面開始搭建

1.創(chuàng)建

首先注解處理器需要javax包的支持,我們的Android環(huán)境下是訪問(wèn)不到j(luò)avax包的,除非我們自己配置。

//app:build.gradle中加入依賴,一定要使用provided files來(lái)引入.
provided files('/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/jre/lib/rt.jar')

所以我們需要?jiǎng)?chuàng)建Java Library包來(lái)提供javax環(huán)境,另外注解處理器要被打包進(jìn)jar包里面才能被系統(tǒng)識(shí)別,這就是選用ava Library的原因,目前注解注解框架均是如此。

image

創(chuàng)建好Module之后就可以寫我們的自定義的注解處理器了。首先需要繼承抽象類AbstractProcessor,然后重寫process()方法。該方法是核心方法,該方法將遍歷源代碼,找出我們想要注解標(biāo)注的元素。單單重寫這一個(gè)方法是不夠的, 在我們的開發(fā)中往往需要重寫init(),getSupportedSourceVersion(),getSupportedAnnotationTypes()這幾個(gè)方法就可以了。另外再Java7及其以后我們還可以使用注解@SupportedSourceVersion()@SupportedAnnotationTypes() 去替代上面的方法,見于該注解有Java版本的限制,所以還是建議直接重寫方法為好。

public class AnnotationTwoProcessor extends AbstractProcessor {

    private Messager messager; //用于打印日志
    private Elements elementUtils; //用于處理元素
    private Filer filer;  //用來(lái)創(chuàng)建java文件或者class文件

    //該方法再編譯期間會(huì)被注入一個(gè)ProcessingEnvironment對(duì)象,該對(duì)象包含了很多有用的工具類。
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
    }

    /**
     * 該方法將一輪一輪的遍歷源代碼
     * @param set  該方法需要處理的注解類型
     * @param roundEnvironment 關(guān)于一輪遍歷中提供給我們調(diào)用的信息.
     * @return  改輪注解是否處理完成 true 下輪或者其他的注解處理器將不會(huì)接收到次類型的注解.用處不大.
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        return false;
    }

    /**
     * 返回我們Java的版本.
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    /**
     * 返回我們將要處理的注解
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<>();
        annotataions.add(MyAnnotion.class.getCanonicalName());
        return annotataions;
    }
}

2.注冊(cè)

注冊(cè)注解處理器的方法有兩種:

第一種: 處理器必須被打包進(jìn)一個(gè)jar包里面,這也是為什么要建立一個(gè)Java Module。該jar包需要有一個(gè)特定路徑resources/META-INF/services的文件javax.annotation.processing.Processor,該文件的路徑和名稱都是特定的,然后將我們聲明注解處理器的全路徑寫到該文件中,這樣Java虛擬機(jī)會(huì)自動(dòng)找該路徑下中我們聲明的處理器。

image

我們?cè)賱?chuàng)建文件夾的時(shí)候一定要確定其命名的準(zhǔn)確性。創(chuàng)建文件的時(shí)候直接右鍵->new file,保證我們的文件的名字為javax.annotation.processing.Processor

image

創(chuàng)建成功之后我們將自定義的注解處理器的全路徑寫到該文件里面。

image

這樣問(wèn)題來(lái)了,如我我們寫了多個(gè)注解處理器該怎么聲明呢?接著看。如果我們一個(gè)Module里面聲明了多個(gè)注解處理器,再該文件中聲明的時(shí)候每個(gè)注解處理器要換行聲明,運(yùn)行的順序就按聲明的順序去執(zhí)行。這里需要對(duì)注解處理器的運(yùn)行順序解釋一下,再編譯期間并不是一個(gè)注解處理器完全的處理結(jié)束再開始下一個(gè)的,而是在掃描一輪源代碼 的時(shí)候注冊(cè)的第一個(gè)處理器先執(zhí)行一輪,然后注冊(cè)的第二個(gè)處理器開始執(zhí)行然后。。。三個(gè)。。四個(gè)。第二輪的時(shí)候還是注冊(cè)的第一個(gè)處理器先執(zhí)行,然后第二個(gè)。。。三個(gè)。。。這里面的深刻解釋會(huì)在下一篇講解,這篇只是使用。

cn.example.wang.processor.AnnotationProcessor
cn.example.wang.processorAnnotationTwoProcessor

第二種:當(dāng)覺得第一種方法配置繁瑣的時(shí)候就會(huì)有新的更簡(jiǎn)潔的方式出現(xiàn)。谷歌公司推出的使用注解去注冊(cè)注解處理器。

  1. 添加依賴,可以去GitHub上面查找最新版本。

    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    
  2. 我們就可以使用了,它的作用和我們手寫的作用是一樣的。不過(guò)注釋的處理器的處理順序跟你類創(chuàng)建的順序是一致的,跟方法一中文件聲明的順序不一樣。

    @AutoService(Processor.class)
    public class AnnotationTwoProcessor extends AbstractProcessor {
    }
    

總的來(lái)說(shuō)方式1的注冊(cè)方式目前僅在EventBus里面有用到,方式2還是推薦使用的,比如:Arouter,BufferKnife等流行框架都是采用的這種方式注冊(cè)的,方便快捷。

3.APP引用注解處理器

再引用注解處理器的Module的時(shí)候坑其實(shí)挺多的。首先我們得確保處理器所在的jar包會(huì)添加到我們運(yùn)行的項(xiàng)目中,注解處理器才會(huì)被得到執(zhí)行,這里呢因?yàn)樵创a不清楚,所以只好自己去試了。Application引入注解處理器包的時(shí)候并不像我們引入其它的Android Module一樣,這里列舉三種種引入方法。

  • plugin: 'com.android.application'

    我們可以使用implementation,compile,api直接引用注解處理器的module,但是會(huì)有一個(gè)編譯錯(cuò)誤:

    Error:Execution failed for task ':app:javaPreCompileDebug'.
    > Annotation processors must be explicitly declared now.  The following dependencies on the compile classpath are found to contain annotation processor.  Please add them to the annotationProcessor configuration.
        - annotation_processor.jar (project :annotation_processor)
      Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior.  Note that this option is deprecated and will be removed in the future.
      See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.
    

    我們需要加一段代碼,代碼位置如下所示,這樣就可以愉快的引入注解處理器了:

    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    includeCompileClasspath = true
                }
            }
        }
    }
    

    另外我們可以用annotationProcessor引入注解處理器,這個(gè)引入方式是為了替換Gradle版本2.2之前的apt引入方法。該引入方式專門用來(lái)處理注解處理器。使用該引入方式的時(shí)候不會(huì)出現(xiàn)錯(cuò)誤提示,也是Android中推薦使用的引入方法,該方式也是主流方式。

    annotationProcessor project(':annotation_processor')
    
  • apply plugin: 'com.android.library'

    前提module一定要被android application引入。module里面引入注解處理器的module的話,基本跟android application中一致,這里說(shuō)一下兩個(gè)不同點(diǎn)annotationProcessor和implementation方式的引入都不會(huì)執(zhí)行注解處理器,真實(shí)的原理并不清楚,只能猜測(cè)是application才能正真的處理注解,所以得把依賴暴露出去。這點(diǎn)再android library的module中一定得注意。不太建議該方式引入。

  • apply plugin: 'java-library'

    前提java library一定要被android application引入。聲明這樣的module 的話我們就可以直接引入注解處理器了,也不用擔(dān)心用什么方式引入。不過(guò)這種場(chǎng)景不太多。

如何確保你的注解處理器已經(jīng)注冊(cè)成功了。首先你已經(jīng)自定義好了一個(gè)注解,并且該注解已經(jīng)用到了你的代碼里面。如下代碼所示,你已經(jīng)設(shè)置了我們要處理的是那種類型的注解(代碼1所示),然后再我們的process方法里面打上日志(代碼2所示),然后點(diǎn)擊Android StudioMake Project按鈕,之后打開Gradle Console窗口看build信息,當(dāng)你發(fā)現(xiàn)信息中打印了代碼2所示的信息之后就表明你的注解處理器已經(jīng)運(yùn)行起來(lái)了。如果沒有打印信息的話嘗試 clean一下項(xiàng)目然后重新Make Project。如果發(fā)現(xiàn)沒有打印日志的話,嘗試查看注解處理器是否已經(jīng)注冊(cè)和注解處理器所在的module是否被android application成功引入。

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {

    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //代碼2
        messager.printMessage(Diagnostic.Kind.NOTE,"日志開始---------------");
        return false;
    }


    //代碼1
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedOptions = new HashSet<>();
        supportedOptions.add(MyAnnotion.class.getCanonicalName());
        return supportedOptions;
    }
    
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }
}

4.自動(dòng)生成Java文件

走到該步驟的時(shí)候,你要確保你的注解處理器已經(jīng)正常的運(yùn)行。我們使用注解處理器處理源碼注解的目的,就是再編譯期間完成我們對(duì)某個(gè)注解的處理工作。比如:BufferKnife再編譯期間會(huì)在使用特定注解的文件路徑生成***—ViewBinding的源文件去處理特定注解。這里用一個(gè)Demo去演示如何生成代碼:

先看我的注解:

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
public @interface MyAnnotion {
    String value() default "ssssss";

}

在我的MainActivity上面使用注解:

@MyAnnotion()
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

接著使用注解處理器去處理注解,生成對(duì)應(yīng)的MainActivity_ViewBinding源文件。

public class AnnotationProcessor extends AbstractProcessor {

    private Messager messager;
    private Elements elementUtils;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE,"日志開始---------------");

        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MyAnnotion.class);
        for (Element element:elementsAnnotatedWith) {
            if(element.getKind() == ElementKind.CLASS){
                TypeElement typeElement = (TypeElement) element;
                PackageElement packageElement = elementUtils.getPackageOf(element);
                String packagePath = packageElement.getQualifiedName().toString();
                String className = typeElement.getSimpleName().toString();
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packagePath + "." + className + "_ViewBinding", typeElement);
                    Writer writer = sourceFile.openWriter();
                    writer.write("package  "+packagePath +";\n");
                    writer.write("import  "+packagePath+"."+className+";\n");
                    writer.write("public class "+className+"_ViewBinding"+"  { \n");
                    writer.write("\n");
                    writer.append("       public "+className +"  targe;\n");
                    writer.write("\n");
                    writer.append("}");
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }


        messager.printMessage(Diagnostic.Kind.NOTE,"日志結(jié)束---------------");
        return false;
    }


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedOptions = new HashSet<>();
        supportedOptions.add(MyAnnotion.class.getCanonicalName());
        return supportedOptions;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }
}

結(jié)果展示:[圖片上傳失敗...(image-c3199d-1536119413375)]

注意一下生成的位置!我們可以直接再我們正常的代碼中應(yīng)用到該文件,因?yàn)樵撐募菚?huì)生成class文件的。

5.總結(jié)

該文章只是介紹了如何搭建起一個(gè)Java注解處理器,沒有更深入的去講解AbstractProcessor類以及我們?cè)偬幚碜⒔獾倪^(guò)程中用到的各種類的API。當(dāng)然接下來(lái)的文章就會(huì)詳細(xì)的介紹注解處理器所使用到的類,方法,屬性等的用法和意義,這一定是史上最全的注解處理器API。之后你會(huì)更加隨心所欲的去構(gòu)建自己的注解框架。

下章節(jié) 史上最全的注解處理器API指導(dǎo)!

歡迎大家留言提出文章中出現(xiàn)的錯(cuò)誤以及不理解的地方,會(huì)在第一時(shí)間進(jìn)行更正和解答~

我的簡(jiǎn)書
我的掘金
我的CSDN
Github
Demo地址,歡迎star

最后編輯于
?著作權(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)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評(píng)論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • 麻麻嘆了一口氣。 :媽咪你怎么了? :上午爸爸說(shuō)讓我在你面前不能太孩子氣。 :啊,那怎么行,我會(huì)不習(xí)慣的! :媽咪...
    招財(cái)貓333333閱讀 219評(píng)論 0 0
  • 今天是9月10日教師節(jié),是所有老師們的節(jié)日,所以我祝福老師們身體健康,每天都快快樂(lè)樂(lè)的。 從一年級(jí)到四年級(jí),有不少...
    祁魯閱讀 164評(píng)論 0 0
  • 周祥娥, 作為一名專業(yè)音樂(lè)教師,今年再次接任了一年級(jí)班主任的工作,面對(duì)41張陌生的小面孔,深感到責(zé)任的重大...
    山泉徐禮杰閱讀 1,346評(píng)論 9 27

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