1. 程式人生 > >kotlin編寫編譯時註解

kotlin編寫編譯時註解

1.定義註解

As裡新建一個Java Library module,必須是Java Library Module
在這裡插入圖片描述

此處命名為route-api

該module存放一些與純java類相關的檔案

1. 定義一個註解 Route

/**
* @param name 路由用的跳轉名字,該key與一個.Class關聯,若不填寫預設為Activity類名去掉“Activity”,如MainActivity為”Main“
* @param appCode app跳轉協議碼,為後臺或前端呼叫本地頁面跳轉的協議如“app://10000"跳轉到登入頁
* @param interceptors 攔截器,用於跳轉到當前目的頁面前先判斷某些條件是否成立,若不成立則攔截跳轉到其他頁面,
* 如,某些頁面需要登入才能跳轉到該頁面則新增一個登入的攔截器,如果沒有登入則跳轉到登入頁,反之,則跳轉到目的頁面
*/
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class Route(val name: String = "", val appCode: Int = -1, val interceptors: Array<KClass<*>> = emptyArray())

該註解用於標記該類自動配置到路由表中

2. 定義一個Config類用於配置自動生成類的包名,類名等

object
RouteConfig { var routePackName = "com.tugo.decor" var routeSimpleName = "AutoRouter" var registerMethod = "registeredToRouter" }

2.實現AbstractProcessor

另外新建一個java Library Module,命名為route-process

1.建立一個TypeElement處理類

class RouteAnnotationInfo(val element: TypeElement) {

    //全限定名,如com.tugou.andromeda.tgkit.MainActivity
val targetQualifiedName = element.qualifiedName.toString(); //簡單名,如MainActivity val targetName = element.simpleName.toString() //攔截器類集合 val interceptors: List<ClassName> //Route註解類的變數,用於獲取某類的Route註解 val annotation: Route init { annotation = element.getAnnotation(Route::class.java) interceptors = iniInterceptors() } //同Route的name val name: String get() { return if (annotation.name.isBlank()) { targetName.replace("Activity", ""); } else { annotation.name } } //同Route的univCode val univCode: Int = annotation.univCode /** * 用於初始化攔截器的類,因為編譯時無法獲取Class檔案,只好通過字串獲取攔截器的類的全限定名以獲得其包名和類名,其他方法可參看 https://www.cnblogs.com/fuckingaway/p/6703021.html,感覺上面寫的太麻煩,我就沒有細看 * 然後將其轉為ClassName物件,ClassName類為javapoet包下的宣告Class的類,用其可以自動匯入包名 ,關於javapoet介紹可看 * @see <a href=" https://www.2cto.com/kf/201609/543893.html ">javapoet——會寫程式碼的“詩人”</a> */ fun iniInterceptors(): List<ClassName> { val resultList = ArrayList<ClassName>(); for (str in getInterceptorStrs(element.getAnnotation(Route::class.java))) { if (str.isNotBlank()) { resultList.add(generateClassName(str)) } } return resultList; } } /** *用於將字串型別的去限定類類名轉換為ClassName類 */ fun generateClassName(str: String): ClassName { val lastIndex = str.lastIndexOf(".") val pack = str.substring(0, lastIndex) val simpleName = str.substring(lastIndex + 1) return ClassName.get(pack, simpleName) } /** *用於獲取註解處理器類的字串 */ fun getInterceptorStrs(annotation: Route): List<String> { val strs = annotation.toString() .substring(Route::class.java.name.length + 1) .replace("(", "").replace(")", "") .split(", ") for (s in strs) { if (s.contains("interceptors=")) { return s.substring("interceptors=".length).split(",") } } return emptyList() }

2.建一個RouteProcess類繼承成AbstractProcessor

@AutoService(Processor::class)
class RouteProcess : AbstractProcessor() {
	/**
     *該方法用於表明該處理器處理那些註解
     */
    override fun getSupportedAnnotationTypes(): Set<String> {
        return setOf<String>(Route::class.java.getCanonicalName())
    }
    /**
     *該方法用於處理註解,並生成程式碼
     */
    override fun process(set: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
        //宣告一個Set集合用於儲存處理類
        val hashSet = HashSet<RouteAnnotationInfo>();
        //獲取所有被Route註解的元素
        val elements = roundEnv.getElementsAnnotatedWith(Route::class.java)
        for (annotatedElement: Element in elements) {
            if (annotatedElement.kind != ElementKind.CLASS) {
                processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "該註解只能用於類", annotatedElement)
                return false
            }
            val typeElement = annotatedElement as TypeElement
            hashSet.add(RouteAnnotationInfo(typeElement))

        }
        //準備寫程式碼
        //ParameterizedTypeName用於引數化類如List<T>,下面程式碼用於宣告一個List<TGPage>類
        val routeTypeList = ParameterizedTypeName.get(List::class.java, TGPage::class.java)//此處TGPage為定義路由表單項的一個類

        val methodProvidePagesBuilder = MethodSpec.methodBuilder("providePages")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(List::class.java)
        //下面生成程式碼為List<TGPage> list=new ArrayList<>()
                .addStatement("\$T list = new \$T<>()", routeTypeList, ArrayList::class.java)

        //"開始寫方法內容"
        for (routeAnnotationInfo in hashSet) {

            methodProvidePagesBuilder.addCode("list.add(new \$T(\$S,\$T.class",
                    TGPage::class.java,
                    routeAnnotationInfo.name,
                    ClassName.get(routeAnnotationInfo.element)
            )

            //生成Class陣列的程式碼
            val codeBlock = CodeBlock.builder();
            codeBlock.add(",\$L", routeAnnotationInfo.univCode)

            if (routeAnnotationInfo.interceptors.isNotEmpty()) {
                codeBlock.add(",new \$T[]{", Class::class.java)
                for (clazz in routeAnnotationInfo.interceptors) {
//                methodProvidePagesBuilder.addComment("測試element:\$L",str)
                    if (routeAnnotationInfo.interceptors.indexOf(clazz) != 0) {
                        codeBlock.add(",")
                    }
                    codeBlock.add("\$T.class", clazz)
//                    methodProvidePagesBuilder.addComment("測試element:\$L", clazz)
                }
                codeBlock.add("}")
            }
            methodProvidePagesBuilder.addCode(codeBlock.build())
                    .addStatement("))")
        }

        methodProvidePagesBuilder.addStatement("return list")

        val registe = MethodSpec.methodBuilder(RouteConfig.registerMethod)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addStatement("\$T.registerPages(\$N())", ClassName.get(RouteConfig.tgRouterPackName, RouteConfig.tgRouterSimpleName), "providePages")
                //tgRouterPackName和tgRouterSimpleName為路由跳轉的類,此處請忽略,該文只介紹如何自動生成路由表
                .build()
        //"開始寫類"

        val routeTable = TypeSpec.classBuilder(RouteConfig.routeSimpleName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(methodProvidePagesBuilder.build())
                .addMethod(registe)
                .build();

        //"寫成java檔案"
        val javaFile: JavaFile = JavaFile.builder(RouteConfig.routePackName, routeTable)
                .build();
        try {
            //僅呼叫此方法會報錯但不影響生成程式碼,但會值中斷Run,所以用try—catch塊包裹住
            javaFile.writeTo(processingEnv.filer)
        } catch (e: IOException) {
            e.printStackTrace();
        }
        return false
    }
}    

MethodSpec用於是用於方法生成的類,通過MethodSpec.methodBuilder(methodName)生成一個MethodSpec的構造器,可以用構造方法

build.addModifiers用於宣告方法的修飾符如public static private等等,可以通過Modifier的列舉傳入

build.returns(Type returnType)宣告方法的返回型別,未宣告的則為void

build…addComment(String commen)用於生成註釋,當你的註解器可以自動生成程式碼時,你可以用其來進行測試資料

build.addStatement(String format, Object… args)為方法增加一行程式碼,會自動新增分號和換行

  • format支援字串模板,同過TT或S,L,L,N來表示一個佔位,args則為前面佔位的實際值,args的個數要與前面佔位符的個數相同

  • TType.classClassNameMainActivity.classMainActivityMainActivity.classT表示Type型別--類或介面等型別的佔位符,物件可以傳.class物件或ClassName物件等,會對其自動導包,注意如果是MainActivity.class,生成的程式碼為MainActivity多以如果你需要生成MainActivity.class你需要在T後面加上.class字串即“$T.class”

  • SS代表字串型別的佔位符,物件必須為字串類;L表示文字型別的字串,包含字串型別,也可以支援整數等型別,具體請自己嘗試

  • N調(),N表示方法名型別的佔位符,注意只會生成方法的名字,方法呼叫要自己加(),如“N()”,“Main”,才表示呼叫了Main()

build.add(String format, Object… args)同上面類似,但不會換行也不會新增分號

CodeBlock是生成程式碼片段的類,方法等同MethodSpec的方法相同

TypeSpec用於生成類,介面等,TypeSpec.classBuilder(String name)可以生成一個TypeSpec的Builder 物件,該Builder 物件可以用於設定類的生成

build.addModifiers(Modifier.PUBLIC, Modifier.FINAL)用於宣告類的修飾符
build.addMethod(methodProvidePagesBuilder.build())用於為類新增方法,引數為MethodSpec型別

JavaFile為用於生成.java檔案JavaFile.builder(String packageName, TypeSpec typeSpec),packageName為該java檔案的包名,typeSpec則為該java檔案的class

javaFile.writeTo(processingEnv.filer)則真正用於將檔案寫入到磁碟中

3.注意

1.要將註解處理器建立在JavaLibrary Module裡

Android的Library module不支援javax包下的檔案,而我們需要的AbstractProcessor確實javax包下的

2.auto-service使用,並用kapt進行註解處理

auto-service用於註冊處理器,compiler庫會生成一個jar,在jar中META-INF/services目錄需要新建一個特殊的檔案javax.annotation.processing.Processor,檔案裡的內容就是宣告你的處理器,而auto-service可以自動生成META-INF/services/javax.annotation.processing.Processor檔案

注意在kotlin中要用kapt "com.google.auto.service:auto-service:1.0-rc2"宣告@AutoService的直接處理器

相關推薦

kotlin編寫編譯註解

1.定義註解 As裡新建一個Java Library module,必須是Java Library Module 此處命名為route-api 該module存放一些與純java類相關的檔案 1. 定義一個註解 Route /** * @param name

Android 如何編寫基於編譯註解的專案

                     一、概述在Android應用開發中,我們常常為了提升開發效率會選擇使用一些基於註解的框架,但是由於反射造成一定執行效率的損耗,所以我們會更青睞於編譯時註解的框架,例如:butterknife免去我們編寫View的初始化以及事件的注入的程式碼。fragmentargs輕

Android 如何編寫基於編譯註解的專案---轉載張鴻洋博文

一、概述 在Android應用開發中,我們常常為了提升開發效率會選擇使用一些基於註解的框架,但是由於反射造成一定執行效率的損耗,所以我們會更青睞於編譯時註解的框架,例如: butterknife免去我們編寫View的初始化以及事件的注入的程式碼。fragmentargs輕鬆的為fragment新增引數資

Kotlin編譯註解,簡單實現ButterKnife

ButterKnife在之前的Android開發中還是比較熱門的工具,幫助Android開發者減少程式碼編寫,而且看起來更加的舒適,於是簡單實現一下ButterKnife,相信把下面的程式碼都搞懂,看ButterKnife的難度就小很多。 今天實現的是編譯時註解,其實執行時註解也一樣能實現ButterKnif

java註解編譯註解RetentionPolicy.CLASS 基本用法

1 前言 我們知道,在日常開發中我們常用的兩種註解是執行時註解和編譯時註解,執行時註解是通過反射來實現註解處理器的,對效能稍微有一點損耗,而編譯時註解是在程式編譯期間生成相應的代理類,替我們完成某些功能。今天我們來講解一下編譯時註解以及寫一個小例子,以便加深對編譯時註解的理解。

編譯註解(三)Arouter原始碼講解

專案中我們有時需要跨模組startActivity,但是這樣需要配置menifest,不方便。這時就需要阿里的一個路由框架Arouter Arouter的使用就不再多說了。這篇文章主要講解他的原始碼 1.初始化 ARouter.init( this ); public sta

編譯註解(一)AbstractProcessor實戰

Java中的註解(Annotation)是一個很神奇的東西,特別現在有很多Android庫都是使用註解的方式來實現的。 我們並不討論那些在執行時(Runtime)通過反射機制執行處理的註解,而是討論在編譯時(Compile time)處理的註解。下面便入手學習下

編譯註解(二)JavaPoet的使用

上一篇文章提到AbstractProcessor中生成java類,可以使用JavaPoet開源庫進行編寫。但是有個問題,addModifier提示無法找到Modifier,其實只要把 compile project(’:libprocess’) 改成 annot

通過編譯註解生成程式碼實現自己的ButterKnife

背景概述 註解的處理除了可以在執行時通過反射機制處理外,還可以在編譯期進行處理。 Java5中提供了apt工具來進行編譯期的註解處理。apt是命令列工具,與之配套的是一套描述“程式在編譯時刻的靜態結構”的API:Mirror API(com.sun.mirr

利用編譯註解生成Java原始碼

我們在編寫註解的時候,需要指定@Retention,有三個可選值,表示註解會被保留到那個階段。 RetentionPolicy.SOURCE       這種型別的Annotations只在原始碼級別保留,編譯時就會被忽略,因此一般用來為編譯器提供額外資訊,以便於檢測錯誤,

使用編譯註解簡單實現類似 ButterKnife 的效果

讀完本文你將瞭解: 什麼是編譯時註解 上篇文章 什麼是註解以及執行時註解的使用 中我們介紹了註解的幾種使用場景,這裡回顧一下: 編譯前提示資訊:註解可以被編譯器用來發現錯誤,或者清除不必要的警告; 編譯時生成程式碼:一些處理器可以在編譯時根據註

Java編譯註解自動生成程式碼

在開始之前,我們首先申明一個非常重要的問題:我們並不討論那些在執行時(Runtime)通過反射機制執行處理的註解,而是討論在編譯時(Compile time)處理的註解。註解處理器是一個在javac中的,用來編譯時掃描和處理的註解的工具。可以為特定的註解,註冊自己的註解處

自定義註解編譯註解(RetentionPolicy.CLASS)(二)——JavaPoet

在使用編譯時註解時,需要在編譯期間對註解進行處理,在這裡我們沒辦法影響程式的執行邏輯,但我們可以進行一些需處理,比如生成一些功能性程式碼來輔助程式的開發,最常見的是生成.java 原始檔,並在程式中可以呼叫到生成的檔案。這樣我們就可以用註解來幫助我們處理一些固定邏輯的重複性

如何除錯編譯註解處理器AnnotationProcessor

本來的話是想跟大家分享如何製作自己的編譯時註解處理器的,後來搜尋了一下發現網上有不少這方面的文章,寫得都很全面很優秀,所以就不獻醜了。如果大家還不知道怎麼寫自己的編譯時註解處理器,可以看下這位大神寫的文章學習下:http://blog.csdn.net/lmj6

Android編譯註解框架4-爬坑

概述 因為有關APT的資料過少,又因為是Java Moudle +Android Moudle的使用方式,在專案編寫過程中,會有一些匪夷所思的奇怪問題~入門階段真的是步履維艱。 這篇部落格就是編寫《Android編譯時註解框架》系列中,將所遇到的一些坑列舉出來,並給出

Java編譯註解應用-生成格式化原始檔

引言 有許多開源框架在編譯時通過註解資訊生成新的原始檔,已到達簡化樣板程式碼的書寫,比如說典型的Builder模式,或者實現框架的功能的橋接程式碼。因為我使用編譯時註解只是想要簡化樣板程式碼,下面我就以Builder模式作為示例。 Builder模式

關於java編譯註解你需要知道的二三事。解除你的顧慮!

做Android開發。大家肯定會關心你的app的效能問題。不知道從何時開始。網上有流傳一句。不要使用註解。用註解會影響效能。這不能說錯。但是也不能說對。這裡普及一下關於註解的一些你需要知道的知識 網上常說的註解。基本是執行時註解。而所說的註解會影響效能。則

ButterKnife編譯註解探祕

安卓中很多有名的開源框架都運用了編譯時註解,如ButterKnife,EventBus , Retrofit , GreenDao等。所以作為一個合格的安卓開發者,學會運用編譯時註解是非常有必要的。 下面就仿照ButterKnife的view的注入寫一個例子來

kotlin結合dagger2使用為什麼在編譯無法自動生成DaggerxxxComponent類

算是一個小坑,卡了我大半天的時間解決方法很簡單,只要將gradle裡面依賴的apt改成kapt就行了,比如dagger2裡面的dagger-compiler,databinding裡面的compiler

ROS知識(16)----如何編譯自動鏈接同一個工作空間的其他包的頭文件(包含message,srv,action自動生成的頭文件)

logs package fin 空間 依賴庫 osc div build 知識 catkin_make編譯時,往往需要自動鏈接同一個工作空間的其他包的頭文件。否則會出現類似如下的錯誤: /home/xx/xx_ws/srcA_package/src/db.hpp:13: