Android 自定義注解(Annotation)

? ? ? ?現(xiàn)在市面上很多框架都有使用到注解骤肛,比如butterknife庫、EventBus庫苔可、Retrofit庫等等。也是一直好奇他們都是怎么做到的袋狞,注解的工作原理是啥焚辅。咱們能不能自己去實(shí)現(xiàn)一個(gè)簡單的注解呢。

? ? ? ?注解(Annotation)是JDK1.5新增加功能苟鸯,注解其實(shí)就是添加在類同蜻、變量、方法早处、參數(shù)等前面的一個(gè)修飾符一個(gè)標(biāo)記而已(不要把他想的太復(fù)雜)湾蔓。比如下面的代碼里面@Override、@IdRes就是注解砌梆。

    @Override
    public <T extends View> T findViewById(@IdRes int id) {
        return getDelegate().findViewById(id);
    }

? ? ? ?上面我們強(qiáng)調(diào)了注解就是一個(gè)修飾符一個(gè)標(biāo)記而且默责。但是通過注解能做的事情確是無窮贬循。在代碼編譯或者運(yùn)行的過程中我們可以找到這些 注解,在找到這些注解之后咱們就可以做很多事情了桃序,比如自動做一些代碼處理(賦值杖虾、檢測、調(diào)用等等)或者干脆生成一些額外的java文件等媒熊。下面會用更加具體的實(shí)例來說明奇适。

? ? ? ?注解的作用:簡化代碼,提高開發(fā)效率芦鳍。

注意哦嚷往,肯定是能提高代碼開發(fā)效率,并不一定能提供程序運(yùn)行效率柠衅。


? ? ? ?接下來我們通過學(xué)習(xí)自定義注解(定義我們自己的注解)來讓大家對注解有一個(gè)深刻的認(rèn)識皮仁。

一、元注解

? ? ? ?在我們自定義注解之前我們需要來先了解下元注解茄茁。元注解是用來定義其他注解的注解(在自定義注解的時(shí)候魂贬,需要使用到元注解來定義我們的注解)。java.lang.annotation提供了四種元注解:@Retention裙顽、 @Target付燥、@Inherited、@Documented愈犹。

元注解是用來修飾注解的注解键科。在自定義注解的時(shí)候我們肯定都是要用到元注解的。因?yàn)槲覀冃枰x我們注解的是方法還是變量漩怎,注解的存活時(shí)間等等勋颖。

元注解 說明
@Target 表明我們注解可以出現(xiàn)的地方。是一個(gè)ElementType枚舉
@Retention 這個(gè)注解的的存活時(shí)間
@Document 表明注解可以被javadoc此類的工具文檔化
@Inherited 是否允許子類繼承該注解勋锤,默認(rèn)為false

1.1授帕、@Target

? ? ? ?@Target元注解用來表明我們注解可以出現(xiàn)的地方核无,參數(shù)是一個(gè)ElementType類型的數(shù)組宵呛,所以@Target可以設(shè)置注解同時(shí)出現(xiàn)在多個(gè)地方油猫。比如既可以出現(xiàn)來類的前面也可以出現(xiàn)在變量的前面。

? ? ? ?@Target元注解ElementType枚舉(用來指定注解可以出現(xiàn)的地方):

@Target-ElementType類型 說明
ElementType.TYPE 接口谈宛、類次哈、枚舉、注解
ElementType.FIELD 字段吆录、枚舉的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法參數(shù)
ElementType.CONSTRUCTOR 構(gòu)造函數(shù)
ElementType.LOCAL_VARIABLE 局部變量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE

1.2窑滞、@Retention

? ? ? ?@Retention表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)。參數(shù)是RetentionPolicy枚舉對象哀卫。

? ? ? ?RetentionPolicy的枚舉類型有(默認(rèn)值為CLASS.):

@Retention-RetentionPolicy類型 說明
RetentionPolicy.SOURCE 注解只保留在源文件巨坊,當(dāng)Java文件編譯成class文件的時(shí)候,注解被遺棄
RetentionPolicy.CLASS 注解被保留到class文件聊训,但jvm加載class文件時(shí)候被遺棄抱究,這是默認(rèn)的生命周期
RetentionPolicy.RUNTIME 注解不僅被保存到class文件中恢氯,jvm加載class文件之后带斑,仍然存在

SOURCE < CLASS < RUNTIME,前者能作用的地方后者一定也能作用.

1.3、@Document

? ? ? ?@Document表明我們標(biāo)記的注解可以被javadoc此類的工具文檔化勋拟。

1.4勋磕、@Inherited

? ? ? ?@Inherited表明我們標(biāo)記的注解是被繼承的。比如敢靡,如果一個(gè)父類使用了@Inherited修飾的注解挂滓,則允許子類繼承該父類的注解。

二啸胧、自定義注解

2.1赶站、自定義運(yùn)行時(shí)注解

? ? ? ?運(yùn)行時(shí)注解:在代碼運(yùn)行的過程中通過反射機(jī)制找到我們自定義的注解,然后做相應(yīng)的事情纺念。

反射:對于任意一個(gè)類贝椿,都能夠知道這個(gè)類的所有屬性和方法;對于任意一個(gè)對象陷谱,都能夠調(diào)用它的任意一個(gè)方法和屬性烙博。

? ? ? ?自定義運(yùn)行是注解大的方面分為兩步:一個(gè)是申明注解、第二個(gè)是解析注解烟逊。

2.1.1渣窜、申明注解

? ? ? ?申明注解步驟:

  1. 通過@Retention(RetentionPolicy.RUNTIME)元注解確定我們注解是在運(yùn)行的時(shí)候使用。
  2. 通過@Target確定我們注解是作用在什么上面的(變量宪躯、函數(shù)乔宿、類等)。
  3. 確定我們注解需要的參數(shù)访雪。

? ? ? ?比如下面一段代碼我們聲明了一個(gè)作用在變量上的BindString運(yùn)行時(shí)注解详瑞。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindString {

    int value();

}

2.1.2、注解解析

? ? ? ?運(yùn)行時(shí)注解的解析我們簡單的分為三個(gè)步驟:

  1. 找到類對應(yīng)的所有屬性或者方法(至于是找類的屬性還是方法就要看我自定義的注解是定義方法上還是屬性上了)冬阳。
  2. 找到添加了我們注解的屬性或者方法蛤虐。
  3. 做我們注解需要自定義的一些操作。

2.1.2.1肝陪、獲取類的屬性和方法

既然注解是我們自定義的驳庭,我肯定事先會確定我們注解是加在屬性上的還是加在方法上的。

? ? ? ?通過Class對象我們就可以很容易的獲取到當(dāng)前類里面所有的方法和屬性了:

Class類里面常用方法介紹(這里我們不僅僅介紹了獲取屬性和方法的,還介紹了一些其他Class里面常用的方法)

    /**
     * 包名加類名
     */
    public String getName();

    /**
     * 類名
     */
    public String getSimpleName();

    /**
     * 返回當(dāng)前類和父類層次的public構(gòu)造方法
     */
    public Constructor<?>[] getConstructors();

    /**
     * 返回當(dāng)前類所有的構(gòu)造方法(public饲常、private和protected)
     * 不包括父類
     */
    public Constructor<?>[] getDeclaredConstructors();

    /**
     * 返回當(dāng)前類所有public的字段蹲堂,包括父類
     */
    public Field[] getFields();

    /**
     * 返回當(dāng)前類所有申明的字段,即包括public贝淤、private和protected柒竞,
     * 不包括父類
     */
    public native Field[] getDeclaredFields();

    /**
     * 返回當(dāng)前類所有public的方法,包括父類
     */
    public Method[] getMethods();

    /**
     * 返回當(dāng)前類所有的方法播聪,即包括public朽基、private和protected,
     * 不包括父類
     */
    public Method[] getDeclaredMethods();

    /**
     * 獲取局部或匿名內(nèi)部類在定義時(shí)所在的方法
     */
    public Method getEnclosingMethod();

    /**
     * 獲取當(dāng)前類的包
     */
    public Package getPackage();

    /**
     * 獲取當(dāng)前類的包名
     */
    public String getPackageName$();

    /**
     * 獲取當(dāng)前類的直接超類的 Type
     */
    public Type getGenericSuperclass();

    /**
     * 返回當(dāng)前類直接實(shí)現(xiàn)的接口.不包含泛型參數(shù)信息
     */
    public Class<?>[] getInterfaces();

    /**
     * 返回當(dāng)前類的修飾符离陶,public,private,protected
     */
    public int getModifiers();

? ? ? ?類里面每個(gè)屬性對應(yīng)一個(gè)對象Field稼虎,每個(gè)方法對應(yīng)一個(gè)對象Method。

2.1.2.2招刨、找到添加注解的屬性或者方法

? ? ? ?上面說道每個(gè)屬性對應(yīng)Field霎俩,每個(gè)方法對應(yīng)Method。而且Field和Method都實(shí)現(xiàn)了AnnotatedElement接口沉眶。都有AnnotatedElement接了我們就可以很容易的找到添加了我們指定注解的方法或者屬性了打却。

AnnotatedElement接口常用方法如下:

    /**
     * 指定類型的注釋是否存在于此元素上
     */
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    /**
     * 返回該元素上存在的指定類型的注解
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    /**
     * 返回該元素上存在的所有注解
     */
    Annotation[] getAnnotations();

    /**
     * 返回該元素指定類型的注解
     */
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }

    /**
     * 返回直接存在與該元素上的所有注釋(父類里面的不算)
     */
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        // Loop over all directly-present annotations looking for a matching one
        for (Annotation annotation : getDeclaredAnnotations()) {
            if (annotationClass.equals(annotation.annotationType())) {
                // More robust to do a dynamic cast at runtime instead
                // of compile-time only.
                return annotationClass.cast(annotation);
            }
        }
        return null;
    }

    /**
     * 返回直接存在該元素岸上某類型的注釋
     */
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
    }

    /**
     * 返回直接存在與該元素上的所有注釋
     */
    Annotation[] getDeclaredAnnotations();

2.1.2.3、做自定義注解需要做的事情

? ? ? ?添加了我們注解的屬性或者方法已經(jīng)拿到了谎倔,之后要做的就是自定義注解自定義的一些事情了柳击。比如在某些特定條件下自動去執(zhí)行我們添加注解的方法。下面我們也會用兩個(gè)具體的實(shí)例來說明传藏。

2.1.3腻暮、運(yùn)行時(shí)注解實(shí)例

? ? ? ?我們通過兩個(gè)簡單的實(shí)例來看下自定義運(yùn)行時(shí)注解是怎么操作的。

2.1.3.1毯侦、通過注解自動創(chuàng)建對象

? ? ? ?代碼過程中哭靖,我們可能經(jīng)常會犯這樣的錯(cuò)誤,定義了一個(gè)對象侈离,但是經(jīng)常忘了創(chuàng)建對象试幽。跑出空指針異常。接下來我們通過自定義一個(gè)AutoWired注解來自動去幫我們創(chuàng)建對象卦碾。

AutoWired注解的聲铺坞,指定注解是在變量上使用,并且在運(yùn)行時(shí)有效洲胖。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {

}

AutoWired注解的解析济榨,找到AutoWired注解的變量,創(chuàng)建對象绿映,在吧對象賦值給AutoWired指定的那個(gè)變量擒滑。

public class AutoWiredProcess {

    public static void bind(final Object object) {
        Class parentClass = object.getClass();
        Field[] fields = parentClass.getFields();
        for (final Field field : fields) {
            AutoWired autoWiredAnnotation = field.getAnnotation(AutoWired.class);
            if (autoWiredAnnotation != null) {
                field.setAccessible(true);
                try {
                    Class<?> autoCreateClass = field.getType();
                    Constructor autoCreateConstructor = autoCreateClass.getConstructor();
                    field.set(object, autoCreateConstructor.newInstance());
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }

            }
        }
    }

}

AutoWired注解的使用腐晾,在onCrate()方法里面調(diào)用了AutoWiredProcess.bind(this);來解析注解。這樣在運(yùn)行的時(shí)候就會自動去創(chuàng)建UserInfo對象丐一。

public class MainActivity extends AppCompatActivity {

    //自動創(chuàng)建對象藻糖,不用我們?nèi)ew UserInfo()了
    @AutoWired
    UserInfo mUserInfo;

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

2.1.3.2、通過注解自動findViewById()

? ? ? ?我們也來簡單的來實(shí)現(xiàn)一個(gè)類似Butterknife 庫里面自動綁定View的一個(gè)功能库车。不用在每個(gè)View都要去寫findViewById來找到這個(gè)View了巨柒。

聲明BindView注解,而且規(guī)定需要一個(gè)int參數(shù)柠衍。int參數(shù)代表View對應(yīng)的id

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {

    int value();
}

解析BindView注解洋满,通過findViewById找到VIew,在把View賦值給BindView注解指向的變量拧略。

public class ButterKnifeProcess {

    /**
     * 綁定Activity
     */
    public static void bind(final Activity activity) {
        Class annotationParent = activity.getClass();
        Field[] fields = annotationParent.getDeclaredFields();
        Method[] methods = annotationParent.getDeclaredMethods();
        // OnClick
        // 找到類里面所有的方法
        for (final Method method : methods) {
            //找到添加了OnClick注解的方法
            OnClick clickMethod = method.getAnnotation(OnClick.class);
            if (clickMethod != null && clickMethod.value().length != 0) {
                for (int id : clickMethod.value()) {
                    final View view = activity.findViewById(id);
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                method.invoke(activity, view);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }

    }

}

使用BindView注解芦岂,onCreate里面調(diào)用了ButterKnifeProcess.bind(this);來解析注解瘪弓。

public class MainActivity extends AppCompatActivity {

    //自動綁定view
    @BindView(R.id.text_abstract_processor)
    TextView mTextView;

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

2.2垫蛆、自定義編譯時(shí)注解

? ? ? ?編譯時(shí)注解就是在編譯的過程中用一個(gè)javac注解處理器來掃描到我們自定義的注解,生成我們需要的一些文件(通常是java文件)腺怯。

? ? ? ?自定義編譯時(shí)注解的步驟:

  1. 聲明注解袱饭。
  2. 編寫注解處理器。
  3. 生成文件(通常是JAVA文件)呛占。

第二步和第三步其實(shí)是柔和在一起的虑乖。我這里為了清晰一點(diǎn)就把他們獨(dú)立開來了。

2.2.1晾虑、聲明注解

? ? ? ?編譯時(shí)注解的聲明和運(yùn)行時(shí)注解的聲明一樣也是三步:

  1. 通過@Retention(RetentionPolicy.TYPE)元注解確定我們注解是在編譯的時(shí)候使用疹味。
  2. 通過@Target確定我們注解是作用在什么上面的(變量、函數(shù)帜篇、類等)糙捺。
  3. 確定我們注解需要的參數(shù)。

? ? ? ?比如下面的代碼我們自定義了一個(gè)作用在類上的編譯時(shí)注解Factory笙隙,并且這個(gè)注解是需要兩個(gè)參數(shù)的洪灯,一個(gè)是Class類型,一個(gè)是String類型竟痰。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
    Class type();

    String id();
}

2.2.2签钩、編寫注解處理器

? ? ? ?和運(yùn)行時(shí)注解的解析不一樣,編譯時(shí)注解的解析需要我們自己去實(shí)現(xiàn)一個(gè)注解處理器坏快。

注解處理器(Annotation Processor)是javac的一個(gè)工具铅檩,它用來在編譯時(shí)掃描和處理注解(Annotation)。一個(gè)注解的注解處理器莽鸿,以Java代碼(或者編譯過的字節(jié)碼)作為輸入昧旨,生成文件(通常是.java文件)作為輸出。而且這些生成的Java文件同咱們手動編寫的Java源代碼一樣可以調(diào)用。(注意:不能修改已經(jīng)存在的java文件代碼)臼予。

? ? ? ?注解處理器所做的工作鸣戴,就是在代碼編譯的過程中,找到我們指定的注解粘拾。然后讓我們更加自己特定的邏輯做出相應(yīng)的處理(通常是生成JAVA文件)窄锅。

? ? ? ?注解處理器的寫法有固定套路的,兩步:

  1. 注冊注解處理器(這個(gè)注解器就是我們第二步自定義的類)缰雇。
  2. 自定義注解處理器類繼承AbstractProcessor入偷。

2.2.2.1、注冊注解處理器

? ? ? ?打包注解處理器的時(shí)候需要一個(gè)特殊的文件 javax.annotation.processing.Processor 在 META-INF/services 路徑下械哟。在javax.annotation.processing.Processor文件里面寫上我們自定義注解處理器的全稱(包加類的名字)如果有多個(gè)注解處理器換行寫入就可以疏之。

? ? ? ?偉大的google為了方便我們注冊注解處理器。給提供了一個(gè)注冊處理器的庫
@AutoService(Processor.class)的注解來簡化我們的操作暇咆。我們只需要在我們自定義的注解處理器類前面加上google的這個(gè)注解锋爪,在打包的時(shí)候就會自動生成javax.annotation.processing.Processor文件,寫入相的信息爸业。不需要我們手動去創(chuàng)建其骄。當(dāng)然了如果你想使用google的這個(gè)注解處理器的庫,必須加上下面的依賴扯旷。

compile 'com.google.auto.service:auto-service:1.0-rc3'

? ? ? ?比如下面的這段代碼就使用上了google提供的這個(gè)注解器處理庫拯爽,會自動注冊注解處理器。

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

}

2.2.2.2钧忽、自定義注解處理器類

? ? ? ?自定義的注解處理器類一定要繼承AbstractProcessor毯炮,否則找不到我們需要的注解。在這個(gè)類里面找到我們需要的注解耸黑。做出相應(yīng)的處理桃煎。

? ? ? ?關(guān)于AbstractProcessor里面的一些函數(shù)我們也做一個(gè)簡單的介紹。

    /**
     * 每個(gè)Annotation Processor必須有一個(gè)空的構(gòu)造函數(shù)崎坊。
     * 編譯期間备禀,init()會自動被注解處理工具調(diào)用,并傳入ProcessingEnvironment參數(shù)奈揍,
     * 通過該參數(shù)可以獲取到很多有用的工具類(Element曲尸,F(xiàn)iler,Messager等)
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    /**
     * 用于指定自定義注解處理器(Annotation Processor)是注冊給哪些注解的(Annotation),
     * 注解(Annotation)指定必須是完整的包名+類名
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
     * 用于指定你的java版本男翰,一般返回:SourceVersion.latestSupported()
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * Annotation Processor掃描出的結(jié)果會存儲進(jìn)roundEnvironment中另患,可以在這里獲取到注解內(nèi)容,編寫你的操作邏輯蛾绎。
     * 注意:process()函數(shù)中不能直接進(jìn)行異常拋出,否則程序會異常崩潰
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }

? ? ? ?注解處理器的核心是process()方法(需要重寫AbstractProcessor類的該方法)昆箕,而process()方法的核心是Element元素鸦列。Element 代表程序的元素,在注解處理過程中鹏倘,編譯器會掃描所有的Java源文件薯嗤,并將源碼中的每一個(gè)部分都看作特定類型的Element。它可以代表包纤泵、類骆姐、接口、方法捏题、字段等多種元素種類玻褪。所有Element肯定是有好幾個(gè)子類。如下所示公荧。

Element子類 解釋
TypeElement 類或接口元素
VariableElement 字段带射、enum常量、方法或構(gòu)造方法參數(shù)循狰、局部變量或異常參數(shù)元素
ExecutableElement 類或接口的方法窟社、構(gòu)造方法,或者注解類型元素
PackageElement 包元素
TypeParameterElement 類晤揣、接口桥爽、方法或構(gòu)造方法元素的泛型參數(shù)

? ? ? ?關(guān)于Element類里面的方法我們也做一個(gè)簡單的介紹:

    /**
     * 返回此元素定義的類型,int,long這些
     */
    TypeMirror asType();

    /**
     * 返回此元素的種類:包昧识、類、接口盗扒、方法跪楞、字段
     */
    ElementKind getKind();

    /**
     * 返回此元素的修飾符:public、private侣灶、protected
     */
    Set<Modifier> getModifiers();

    /**
     * 返回此元素的簡單名稱(類名)
     */
    Name getSimpleName();

    /**
     * 返回封裝此元素的最里層元素甸祭。
     * 如果此元素的聲明在詞法上直接封裝在另一個(gè)元素的聲明中,則返回那個(gè)封裝元素褥影;
     * 如果此元素是頂層類型池户,則返回它的包;
     * 如果此元素是一個(gè)包凡怎,則返回 null校焦;
     * 如果此元素是一個(gè)泛型參數(shù),則返回 null.
     */
    Element getEnclosingElement();

    /**
     * 返回此元素直接封裝的子元素
     */
    List<? extends Element> getEnclosedElements();

    /**
     * 返回直接存在于此元素上的注解
     * 要獲得繼承的注解统倒,可使用 getAllAnnotationMirrors
     */
    List<? extends AnnotationMirror> getAnnotationMirrors();

    /**
     * 返回此元素上存在的指定類型的注解
     */
    <A extends Annotation> A getAnnotation(Class<A> var1);

關(guān)于TypeElement寨典、VariableElement、ExecutableElement房匆、PackageElement耸成、TypeParameterElement每個(gè)類特有的方法我們這里就沒有介紹了报亩,大家可以到相應(yīng)的源碼文件里面去看一看。

? ? ? ?自定義處理器的過程中我們除了要了解Element類和他的子類的用法井氢,還有四個(gè)幫助類也是需要我們了解的弦追。Elements、Types花竞、Filer骗卜、Messager。

注解解析器幫助類 解釋
Elements 一個(gè)用來處理Element的工具類
Types 一個(gè)用來處理TypeMirror的工具類
Filer 用于創(chuàng)建文件(比如創(chuàng)建class文件)
Messager 用于輸出左胞,類似printf函數(shù)

? ? ? ?這四個(gè)幫助類都可以在init()函數(shù)里面通過ProcessingEnvironment獲取到寇仓。類似如下的代碼獲取

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

    /**
     * 用來處理TypeMirror的工具類
     */
    private Types                              mTypeUtils;
    /**
     * 用于創(chuàng)建文件
     */
    private Filer                              mFiler;
    /**
     * 用于打印信息
     */
    private Messager                           mMessager;
    ...

    /**
     * 獲取到Types、Filer烤宙、Messager遍烦、Elements
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mTypeUtils = processingEnvironment.getTypeUtils();
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        ...
    }

    ...


}

2.2.3、生成文件

? ? ? ?生成文件躺枕,通常是生成一個(gè)java文件服猪。直接調(diào)用幫助類Filer的createSourceFile()函數(shù)就可以創(chuàng)建一個(gè)java文件。之后就是在這個(gè)java文件里面寫入我們需要的內(nèi)容了拐云。為了提高大家的開發(fā)效率推薦兩個(gè)寫java源文件的開源庫FileWriter和JavaPoet罢猪。兩個(gè)庫用起來也很簡單,這里我們就不深入進(jìn)去了叉瘩。生成文件這一部分的內(nèi)容非常的簡答膳帕。具體可以參考我們下編譯時(shí)注解實(shí)例。

JavaWrite是JavaPoet增強(qiáng)版薇缅。

2.2.4危彩、編譯時(shí)注解實(shí)例

? ? ? ?從網(wǎng)上找了一個(gè)非常全面自定義編譯時(shí)注解的例子。例子來源于 https://blog.csdn.net/github_35180164/article/details/52055994 通過自定義注解實(shí)現(xiàn)工廠模式泳桦。每個(gè)工廠模式通常都會有一個(gè)相應(yīng)的Factory的幫助類來選擇具體的工廠類汤徽,我們現(xiàn)在就想通過編譯時(shí)注解來自動生成這個(gè)Factory的幫助類,不用我們?nèi)ナ謩泳帉懥恕?/p>

Peple抽象類

public abstract class People {

    public abstract String getName();

    public abstract int getAge();

    public abstract int getSex();

}

Male類實(shí)現(xiàn)了People類灸撰,并且添加了@Factory注解

@Factory(id = "Male", type = People.class)
public class Male extends People{

    @Override
    public String getName() {
        return "男生";
    }

    @Override
    public int getAge() {
        return 28;
    }

    @Override
    public int getSex() {
        return 0;
    }
}

Female類實(shí)現(xiàn)了People類谒府,并且添加了@Factory注解

@Factory(id = "Female", type = People.class)
public class Female extends People {

    @Override
    public String getName() {
        return "女生";
    }

    @Override
    public int getAge() {
        return 27;
    }

    @Override
    public int getSex() {
        return 1;
    }
}

根據(jù)上面添加的注解,我們會去自動生成一個(gè)PeopleFactory類浮毯,而且里面的內(nèi)容也編譯的時(shí)候自動生成的完疫,內(nèi)容如下。

public class PeopleFactory {

  public People create(String id) {
    if (id == null) {
      throw new IllegalArgumentException("id is null!");
    }
    if ("Female".equals(id)) {
      return new com.tuacy.annotationlearning.annotation.abstractprocessor.Female();
    }

    if ("Male".equals(id)) {
      return new com.tuacy.annotationlearning.annotation.abstractprocessor.Male();
    }

    throw new IllegalArgumentException("Unknown id = " + id);
  }
}

? ? ? ?為了實(shí)現(xiàn)上述功能亲轨,我們在Android Studio里面新建一個(gè)project趋惨。然后再新建一個(gè)annotationprocess的module,新建module的時(shí)候選擇Java Library惦蚊。在annotationprocess里面寫我們注解的申明和注解的處理器虾。

? ? ? ?先申明一個(gè)Factory的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Factory {

    /**
     * 工廠的名字
     */
    Class type();

    /**
     * 用來表示生成哪個(gè)對象的唯一id
     */
    String id();


}

? ? ? ?在自定義一個(gè)FactoryProcessor注解處理器繼承AbstractProcessor讯嫂。FactoryProcessor代碼里面的內(nèi)容比較多這里我就不粘貼出來了。無非就是找到我們自定義的注解兆沙,然后做一些相應(yīng)的判斷欧芽,最后生成java文件代碼。相應(yīng)的代碼大家可以在下面給出的DEMO里面看到葛圃,DEMO里面的注釋備注寫的也非常詳細(xì)千扔。生成JAVA文件使用的是JavaWriter庫。

? ? ? ?最后我們把a(bǔ)nnotationprocess module里面的代碼打成jar包放到我們需要的工程里面去(同時(shí)把javawriter-2.5.1.jar也拷貝進(jìn)去)库正。使用就和我們上面說的People工廠一樣使用就OK了曲楚。


? ? ? ?本文DEMO下載地址

? ? ? ?關(guān)于自定義注解的內(nèi)容,我們就說的就這么多褥符,希望能給大家起到一個(gè)拋磚引玉的作用龙誊,如果大家對DEMO里面的代碼有什么疑問歡迎留言指出。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喷楣,一起剝皮案震驚了整個(gè)濱河市趟大,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铣焊,老刑警劉巖逊朽,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曲伊,居然都是意外死亡叽讳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門熊昌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绽榛,“玉大人,你說我怎么就攤上這事婿屹。” “怎么了推溃?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵昂利,是天一觀的道長。 經(jīng)常有香客問我铁坎,道長蜂奸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任硬萍,我火速辦了婚禮扩所,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朴乖。我一直安慰自己祖屏,他們只是感情好助赞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袁勺,像睡著了一般雹食。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上期丰,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天群叶,我揣著相機(jī)與錄音,去河邊找鬼钝荡。 笑死街立,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埠通。 我是一名探鬼主播赎离,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼植阴!你這毒婦竟也來了蟹瘾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤掠手,失蹤者是張志新(化名)和其女友劉穎憾朴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喷鸽,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡众雷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了做祝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾省。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖混槐,靈堂內(nèi)的尸體忽然破棺而出编兄,到底是詐尸還是另有隱情,我是刑警寧澤声登,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布狠鸳,位于F島的核電站,受9級特大地震影響悯嗓,放射性物質(zhì)發(fā)生泄漏件舵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一脯厨、第九天 我趴在偏房一處隱蔽的房頂上張望铅祸。 院中可真熱鬧,春花似錦合武、人聲如沸临梗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夜焦。三九已至壳澳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茫经,已是汗流浹背巷波。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卸伞,地道東北人抹镊。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像荤傲,于是被迫代替她去往敵國和親垮耳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容