免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
基礎篇:帶你從頭到尾玩轉(zhuǎn)注解

前面寫了Android 開發(fā):由模塊化到組件化(一),很多小伙伴來問怎么沒有Demo啊?之所以沒有立刻放demo的原因在還有許多技術(shù)點沒說完.

今天我們就來細細評味Java當中Annotation,也就是我們常說的注解.

本文按照以下順序進行:元數(shù)據(jù)->元注解->運行時注解->編譯時注解處理器.


什么是元數(shù)據(jù)(metadata)

元數(shù)據(jù)由metadata譯來,所謂的元數(shù)據(jù)就是“關(guān)于數(shù)據(jù)的數(shù)據(jù)”,更通俗的說就是描述數(shù)據(jù)的數(shù)據(jù),對數(shù)據(jù)及信息資源的描述性信息.比如說一個文本文件,有創(chuàng)建時間,創(chuàng)建人,文件大小等數(shù)據(jù),這都可以理解為是元數(shù)據(jù).

在java中,元數(shù)據(jù)以標簽的形式存在java代碼中,它的存在并不影響程序代碼的編譯和執(zhí)行,通常它被用來生成其它的文件或運行時知道被運行代碼的描述信息。java當中的javadoc和注解都屬于元數(shù)據(jù).

什么是注解(Annotation)?

注解是從java 5.0開始加入,可以用于標注包,類,方法,變量等.比如我們常見的@Override,再或者Android源碼中的@hide,@systemApi,@privateApi等

對于@Override,多數(shù)人往往都是知其然而不知其所以然,今天我就來聊聊Annotation背后的秘密,開始正文.


元注解

元注解就是定義注解的注解,是java提供給我們用于定義注解的基本注解.在java.lang.annotation包中我們可以看到目前元注解共有以下幾個:

  1. @Retention
  2. @Target
  3. @Inherited
  4. @Documented
  5. @interface

下面我們將集合@Override注解來解釋著5個基本注解的用法.

@interface

@interface是java中用于聲明注解類的關(guān)鍵字.使用該注解表示將自動繼承java.lang.annotation.Annotation類,該過程交給編譯器完成.

因此我們想要定義一個注解只需要如下做即可,以@Override注解為例

public @interface Override {}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

需要注意:在定義注解時,不能繼承其他注解或接口.

@Retention

@Retention:該注解用于定義注解保留策略,即定義的注解類在什么時候存在(源碼階段 or 編譯后 or 運行階段).該注解接受以下幾個參數(shù):RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,其具體使用及含義如下:

注解保留策略 含義
@Retention(RetentionPolicy.SOURCE) 注解僅在源碼中保留,class文件中不存在
@Retention(RetentionPolicy.CLASS) 注解在源碼和class文件中都存在,但運行時不存在,即運行時無法獲得,該策略也是默認的保留策略
@Retention(RetentionPolicy.RUNTIME) 注解在源碼,class文件中存在且運行時可以通過反射機制獲取到

來看一下@Override注解的保留策略:

@Retention(RetentionPolicy.SOURCE)public @interface Override {}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

這表明@Override注解只在源碼階段存在,javac在編譯過程中去去掉該注解.

@Target

該注解用于定義注解的作用目標,即注解可以用在什么地方,比如是用于方法上還是用于字段上,該注解接受以下參數(shù):

作用目標 含義
@Target(ElementType.TYPE) 用于接口(注解本質(zhì)上也是接口),類,枚舉
@Target(ElementType.FIELD) 用于字段,枚舉常量
@Target(ElementType.METHOD) 用于方法
@Target(ElementType.PARAMETER) 用于方法參數(shù)
@Target(ElementType.CONSTRUCTOR) 用于構(gòu)造參數(shù)
@Target(ElementType.LOCAL_VARIABLE) 用于局部變量
@Target(ElementType.ANNOTATION_TYPE) 用于注解
@Target(ElementType.PACKAGE) 用于包

以@Override為例,不難看出其作用目標為方法:

@Target(ElementType.METHOD)public @interface Override {}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

到現(xiàn)在,通過@interface,@Retention,@Target已經(jīng)可以完整的定義一個注解,來看@Override完整定義:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

@Inherited

默認情況下,我們自定義的注解用在父類上不會被子類所繼承.如果想讓子類也繼承父類的注解,即注解在子類也生效,需要在自定義注解時設置@Inherited.一般情況下該注解用的比較少.

@Documented

該注解用于描述其它類型的annotation應該被javadoc文檔化,出現(xiàn)在api doc中.
比如使用該注解的@Target會出出現(xiàn)在api說明中.

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target {    ElementType[] value();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

借助@Interface,@Target,@Retention,@Inherited,@Documented這五個元注解,我們就可以自定義注解了,其中前三個注解是任何一個注解都必備具備的.

你以為下面會直接來將如何自定義注解嘛?不,你錯了,我們還是來聊聊java自帶的幾個注解.

系統(tǒng)注解

java設計者已經(jīng)為我們自定義了幾個常用的注解,我們稱之為系統(tǒng)注解,主要是這三個:

系統(tǒng)注解 含義
@Override 用于修飾方法,表示此方法重寫了父類方法
@Deprecated 用于修飾方法,表示此方法已經(jīng)過時
@SuppressWarnnings 該注解用于告訴編譯器忽視某類編譯警告

如果你已經(jīng)完全知道這三者的用途,跳過這一小節(jié),直接往下看.

@Override

它用作標注方法,說明被標注的方法重寫了父類的方法,其功能類似斷言.如果在一個沒有重寫父類方法的方法上使用該注解,java編譯器將會以一個編譯錯誤提示:

@Deprecated

當某個類型或者成員使用該注解時意味著
編譯器不推薦開發(fā)者使用被標記的元素.另外,該注解具有”傳遞性”,子類中重寫該注解標記的方法,盡管子類中的該方法未使用該注解,但編譯器仍然報警.

public class SimpleCalculator {    @Deprecated    public int add(int x, int y) {        return x+y;    }}public class MultiplCalculator extends SimpleCalculator {    // 重寫SimpleCalculator中方法,但不使用@Deprecated    public int add(int x, int y) {        return  Math.abs(x)+Math.abs(y);    }}//test codepublic class Main {    public static void main(String[] args) {        new SimpleCalculator().add(3, 4);        new MultiplCalculator().add(3,5);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

對于像new SimpleCalculator().add(3,4)這種直接調(diào)用的,Idea會直接提示,而像第二種則不是直接提示:

但是在編譯過程中,編譯器都會警告:

需要注意@Deprecated和@deprecated這兩者的區(qū)別,前者被javac識別和處理,而后者則是被javadoc工具識別和處理.因此當我們需要在源碼標記某個方法已經(jīng)過時應該使用@Deprecated,如果需要在文檔中說明則使用@deprecated,因此可以這么:

public class SimpleCalculator {    /**     * @param x     * @param y     * @return     *      * @deprecated deprecated As of version 1.1,     * replace by <code>SimpleCalculator.add(double x,double y)</code>     */    @Deprecated    public int add(int x, int y) {        return x+y;    }    public double add(double x,double y) {        return x+y;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

@SuppressWarnning

該注解被用于有選擇的關(guān)閉編譯器對類,方法,成員變量即變量初始化的警告.該注解可接受以下參數(shù):

參數(shù) 含義
deprecated 使用已過時類,方法,變量
unchecked 執(zhí)行了未檢查的轉(zhuǎn)告時的警告,如使用集合是為使用泛型來制定集合保存時的類型
fallthrough 使用switch,但是沒有break時
path 類路徑,源文件路徑等有不存在的路徑
serial 可序列化的類上缺少serialVersionUID定義時的警告
finally 任何finally字句不能正常完成時的警告
all 以上所有情況的警告

滋溜一下,我們飛過了2016年,不,是看完了上一節(jié).繼續(xù)往下飛.


自定義注解

了解完系統(tǒng)注解之后,現(xiàn)在我們就可以自己來定義注解了,通過上面@Override的實例,不難看出定義注解的格式如下:

public @interface 注解名 {定義體}
  • 1
  • 1

定義體就是方法的集合,每個方法實則是聲明了一個配置參數(shù).方法的名稱作為配置參數(shù)的名稱,方法的返回值類型就是配置參數(shù)的類型.和普通的方法不一樣,可以通過default關(guān)鍵字來聲明配置參數(shù)的默認值.

需要注意:

  1. 此處只能使用public或者默認的defalt兩個權(quán)限修飾符
  2. 配置參數(shù)的類型只能使用基本類型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation
  3. 對于只含有一個配置參數(shù)的注解,參數(shù)名建議設置中value,即方法名為value
  4. 配置參數(shù)一旦設置,其參數(shù)值必須有確定的值,要不在使用注解的時候指定,要不在定義注解的時候使用default為其設置默認值,對于非基本類型的參數(shù)值來說,其不能為null.

像@Override這樣,沒有成員定義的注解稱之為標記注解.

現(xiàn)在我們來自定義個注解@UserMeta,這個注解目前并沒啥用,就是為了演示一番:

@Documented@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)public @interface UserMeta {    public int id() default 0;    public String name() default "";    public int age() default ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

有了米飯,沒有筷子沒法吃啊(手抓飯的走開),下面來看看如何處理注解.

注解處理器

上面我們已經(jīng)學會了如何定義注解,要想注解發(fā)揮實際作用,需要我們?yōu)樽⒔饩帉懴鄳淖⒔馓幚砥?根據(jù)注解的特性,注解處理器可以分為運行時注解處理和編譯時注解處理器.運行時處理器需要借助反射機制實現(xiàn),而編譯時處理器則需要借助APT來實現(xiàn).

無論是運行時注解處理器還是編譯時注解處理器,主要工作都是讀取注解及處理特定注解,從這個角度來看注解處理器還是非常容易理解的.

先來看看如何編寫運行時注解處理器.

運行時注解處理器

熟悉java反射機制的同學一定對java.lang.reflect包非常熟悉,該包中的所有api都支持讀取運行時Annotation的能力,即屬性為@Retention(RetentionPolicy.RUNTIME)的注解.

在java.lang.reflect中的AnnotatedElement接口是所有程序元素的(Class,Method)父接口,我們可以通過反射獲取到某個類的AnnotatedElement對象,進而可以通過該對象提供的方法訪問Annotation信息,常用的方法如下:

方法 含義
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 返回該元素上存在的制定類型的注解
Annotation[] getAnnotations() 返回該元素上存在的所有注解
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 返回該元素指定類型的注解
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) 返回直接存在與該元素上的所有注釋
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) 返回直接存在該元素岸上某類型的注釋
Annotation[] getDeclaredAnnotations() 返回直接存在與該元素上的所有注釋

編寫運行時注解大體就需要了解以上知識點,下面來做個小實驗.

簡單示例

首先我們用一個簡單的實例來介紹如何編寫運行時注解處理器:我們的系統(tǒng)中存在一個User實體類:

public class User {    private int id;    private int age;    private String name;    @UserMeta(id=1,name="dong",age = 10)    public User() {    }    public User(int id, int age, String name) {        this.id = id;        this.age = age;        this.name = name;    }  //...省略setter和getter方法    @Override    public String toString() {        return "User{" +                "id=" + id +                ", age=" + age +                ", name='" + name + '\'' +                '}';    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

我們希望可以通過@UserMeta(id=1,name="dong",age = 10)(這個注解我們在上面提到了)來為設置User實例的默認值。

自定義注解類如下:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.CONSTRUCTOR)public @interface UserMeta {    public int id() default 0;    public String name() default "";    public int age() default 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

該注解類作用于構(gòu)造方法,并在運行時存在,這樣我們就可以在運行時通過反射獲取注解進而為User實例設值,看看如何處理該注解吧.

運行時注解處理器:

public class AnnotationProcessor {    public static void init(Object object) {        if (!(object instanceof User)) {            throw new IllegalArgumentException("[" + object.getClass().getSimpleName() + "] isn't type of User");        }        Constructor[] constructors = object.getClass().getDeclaredConstructors();        for (Constructor constructor : constructors) {            if (constructor.isAnnotationPresent(UserMeta.class)) {                UserMeta userFill = (UserMeta) constructor.getAnnotation(UserMeta.class);                int age = userFill.age();                int id = userFill.id();                String name = userFill.name();                ((User) object).setAge(age);                ((User) object).setId(id);                ((User) object).setName(name);            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

測試代碼:

public class Main {    public static void main(String[] args) {        User user = new User();        AnnotationProcessor.init(user);        System.out.println(user.toString());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

運行測試代碼,便得到我們想要的結(jié)果:

User{id=1, age=10, name=’dong’}

這里通過反射獲取User類聲明的構(gòu)造方法,并檢測是否使用了@UserMeta注解。然后從注解中獲取參數(shù)值并將其賦值給User對象。

正如上面提到,運行時注解處理器的編寫本質(zhì)上就是通過反射獲取注解信息,隨后進行其他操作。編譯一個運行時注解處理器就是這么簡單。運行時注解通常多用于參數(shù)配置類模塊。

自己動手編寫B(tài)utterKnife

對從事Android開發(fā)的小伙伴而言,ButterKnife可謂是神兵利器,能極大的減少我們書寫findViewById(XXX).現(xiàn)在,我們就利用剛才所學的運行時注解處理器來編寫一個簡化版的ButterKnife。

自定義注解:

//該注解用于配置layout資源@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentView {    int value();//只有一個返回時,可用value做名稱,這樣在使用的時候就不需要使用的名稱進行標志}//該注解用于配置控件ID@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {    int id();    boolean clickable() default  false;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

自定義運行時注解:

public class ButterKnife {    //view控件    public static void initViews(Object object, View sourceView){        //獲取該類聲明的成員變量        Field[] fields = object.getClass().getDeclaredFields();        for (Field field : fields){            //獲取該成員變量上使用的ViewInject注解            ViewInject viewInject = field.getAnnotation(ViewInject.class);            if(viewInject != null){                int viewId = viewInject.id();//獲取id參數(shù)值                boolean clickable = viewInject.clickable();//獲取clickable參數(shù)值                if(viewId != -1){                    try {                        field.setAccessible(true);                        field.set(object, sourceView.findViewById(viewId));                        if(clickable == true){                            sourceView.findViewById(viewId).setOnClickListener((View.OnClickListener) (object));                        }                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }        }    }    //布局資源    public static void initLayout(Activity activity){        Class<? extends Activity> activityClass =  activity.getClass();        ContentView contentView = activityClass.getAnnotation(ContentView.class);        if(contentView != null){            int layoutId = contentView.value();            try {                //反射執(zhí)行setContentView()方法                Method method = activityClass.getMethod("setContentView", int.class);                method.invoke(activity, layoutId);            } catch (Exception e) {                e.printStackTrace();            }        }    }    public static void init(Activity activity) {        initLayout(activity);        initViews(activity,activity.getWindow().getDecorView());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

測試代碼:

@ContentView(id=R.layout.activity_main)public class MainActivity extends Activity implements View.OnClickListener {    @ViewInject(id=R.id.tvDis,clickable = true)    private TextView tvDis;    @ViewInject(id=R.id.btnNew,clickable =true)    private Button btnNew;    @ViewInject(id =R.id.btnScreenShot,clickable = true)    private Button btnScreenShot;    @ViewInject(id =R.id.imgContainer)    private ImageView imgContainer;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        AnnotationUtil.inJect(this);    }    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.tvDis:                break;            case R.id.btnNew:                break;            case R.id.btnScreenShot:                break;        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

一個簡單的ButterKnife就實現(xiàn)了,是不是非常簡單。下面我們就進入本文的最重要的一點:編譯時注解處理器。

編譯時注解處理器

不同于運行時注解處理器,編寫編譯時注解處理器(Annotation Processor Tool).

APT用于在編譯時期掃描和處理注解信息.一個特定的注解處理器可以以java源碼文件或編譯后的class文件作為輸入,然后輸出另一些文件,可以是.java文件,也可以是.class文件,但通常我們輸出的是.java文件.(注意:并不是對源文件修改).如果輸出的是.java文件,這些.java文件回合其他源碼文件一起被javac編譯.

你可能很納悶,注解處理器是到底是在什么階段介入的呢?好吧,其實是在javac開始編譯之前,這也就是通常我們?yōu)槭裁丛敢廨敵?java文件的原因.

注解最早是在java 5引入,主要包含apt和com.sum.mirror包中相關(guān)mirror api,此時apt和javac是各自獨立的。從java 6開始,注解處理器正式標準化,apt工具也被直接集成在javac當中。

我們還是回到如何編寫編譯時注解處理器這個話題上,編譯一個編譯時注解處理主要分兩步:

  1. 繼承AbstractProcessor,實現(xiàn)自己的注解處理器
  2. 注冊處理器,并打成jar包

看起來很簡單不是么?來慢慢的看看相關(guān)的知識點吧.

自定義注解處理器

首先來看一下一個標準的注解處理器的格式:

public class MyAnnotationProcessor extends AbstractProcessor {    @Override    public Set<String> getSupportedAnnotationTypes() {        return super.getSupportedAnnotationTypes();    }    @Override    public SourceVersion getSupportedSourceVersion() {        return super.getSupportedSourceVersion();    }    @Override    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);    }    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        return false;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

來簡單的了解下其中5個方法的作用

方法 作用
init(ProcessingEnvironment processingEnv) 該方法有注解處理器自動調(diào)用,其中ProcessingEnvironment類提供了很多有用的工具類:Filter,Types,Elements,Messager等
getSupportedAnnotationTypes() 該方法返回字符串的集合表示該處理器用于處理那些注解
getSupportedSourceVersion() 該方法用來指定支持的java版本,一般來說我們都是支持到最新版本,因此直接返回SourceVersion.latestSupported()即可
process(Set annotations, RoundEnvironment roundEnv) 該方法是注解處理器處理注解的主要地方,我們需要在這里寫掃描和處理注解的代碼,以及最終生成的java文件。其中需要深入的是RoundEnvironment類,該用于查找出程序元素上使用的注解

編寫一個注解處理器首先要對ProcessingEnvironment和RoundEnvironment非常熟悉。接下來我們一覽這兩個類的風采.首先來看一下ProcessingEnvironment類:

public interface ProcessingEnvironment {    Map<String,String> getOptions();    //Messager用來報告錯誤,警告和其他提示信息    Messager getMessager();    //Filter用來創(chuàng)建新的源文件,class文件以及輔助文件    Filer getFiler();    //Elements中包含用于操作Element的工具方法    Elements getElementUtils();     //Types中包含用于操作TypeMirror的工具方法    Types getTypeUtils();    SourceVersion getSourceVersion();    Locale getLocale();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

重點來認識一下Element,Types和Filer。Element(元素)是什么呢?

Element

element表示一個靜態(tài)的,語言級別的構(gòu)件。而任何一個結(jié)構(gòu)化文檔都可以看作是由不同的element組成的結(jié)構(gòu)體,比如XML,JSON等。這里我們用XML來示例:

<root>  <child>    <subchild>.....</subchild>  </child></root>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

這段xml中包含了三個元素:<root>,<child>,<subchild>,到現(xiàn)在你已經(jīng)明白元素是什么。對于java源文件來說,他同樣是一種結(jié)構(gòu)化文檔:

package com.closedevice;             //PackageElementpublic class Main{                  //TypeElement    private int x;                  //VariableElement    private Main(){                 //ExecuteableElement    }    private void print(String msg){ //其中的參數(shù)部分String msg為TypeElement    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

對于java源文件來說,Element代表程序元素:包,類,方法都是一種程序元素。另外如果你對網(wǎng)頁解析工具jsoup熟悉,你會覺得操作此處的element是非常容易,關(guān)于jsoup不在本文講解之內(nèi)。

接下來看看看各種Element之間的關(guān)系圖圖,以便有個大概的了解:

元素 含義
VariableElement 代表一個 字段, 枚舉常量, 方法或者構(gòu)造方法的參數(shù), 局部變量及 異常參數(shù)等元素
PackageElement 代表包元素
TypeElement 代表類或接口元素
ExecutableElement 代碼方法,構(gòu)造函數(shù),類或接口的初始化代碼塊等元素,也包括注解類型元素
TypeMirror

這三個類也需要我們重點掌握:
DeclaredType代表聲明類型:類類型還是接口類型,當然也包括參數(shù)化類型,比如Set<String>,也包括原始類型

TypeElement代表類或接口元素,而DeclaredType代表類類型或接口類型。

TypeMirror代表java語言中的類型.Types包括基本類型,聲明類型(類類型和接口類型),數(shù)組,類型變量和空類型。也代表通配類型參數(shù),可執(zhí)行文件的簽名和返回類型等。TypeMirror類中最重要的是getKind()方法,該方法返回TypeKind類型,為了方便大家理解,這里附上其源碼:

public enum TypeKind {    BOOLEAN,BYTE,SHORT,INT,LONG,CHAR,FLOAT,DOUBLE,VOID,NONE,NULL,ARRAY,DECLARED,ERROR,  TYPEVAR,WILDCARD,PACKAGE,EXECUTABLE,OTHER,UNION,INTERSECTION;    public boolean isPrimitive() {        switch(this) {        case BOOLEAN:        case BYTE:        case SHORT:        case INT:        case LONG:        case CHAR:        case FLOAT:        case DOUBLE:            return true;        default:            return false;        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

簡單來說,Element代表源代碼,TypeElement代表的是源碼中的類型元素,比如類。雖然我們可以從TypeElement中獲取類名,TypeElement中不包含類本身的信息,比如它的父類,要想獲取這信息需要借助TypeMirror,可以通過Element中的asType()獲取元素對應的TypeMirror。

然后來看一下RoundEnvironment,這個類比較簡單,一筆帶過:

public interface RoundEnvironment {    boolean processingOver();     //上一輪注解處理器是否產(chǎn)生錯誤    boolean errorRaised();     //返回上一輪注解處理器生成的根元素    Set<? extends Element> getRootElements();   //返回包含指定注解類型的元素的集合    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);    //返回包含指定注解類型的元素的集合    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
Filer

Filer用于注解處理器中創(chuàng)建新文件。具體用法在下面示例會做演示.另外由于Filer用起來實在比較麻煩,后面我們會使用javapoet簡化我們的操作.

好了,關(guān)于AbstractProcessor中一些重要的知識點我們已經(jīng)看完了.假設你現(xiàn)在已經(jīng)編寫完一個注解處理器了,下面,要做什么呢?

打包并注冊.

自定義的處理器如何才能生效呢?為了讓java編譯器或能夠找到自定義的注解處理器我們需要對其進行注冊和打包:自定義的處理器需要被打成一個jar,并且需要在jar包的META-INF/services路徑下中創(chuàng)建一個固定的文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中需要填寫自定義處理器的完整路徑名,有幾個處理器就需要填寫幾個。

從java 6之后,我們只需要將打出的jar防止到項目的buildpath下即可,javac在運行的過程會自動檢查javax.annotation.processing.Processor注冊的注解處理器,并將其注冊上。而java 5需要單獨使用apt工具,java 5想必用的比較少了,就略過吧.

到現(xiàn)在為止,已經(jīng)大體的介紹了與注解處理器相關(guān)的一些概念,最終我們需要獲得是一個包含注解處理器代碼的jar包.

接下來,來實踐一把.

簡單實例

用個簡單的示例,來演示如何在Gradle來創(chuàng)建一個編譯時注解處理器,為了方便起見,這里就直接借助Android studio.當然你也可以采用maven構(gòu)建.

首先創(chuàng)建AnnotationTest工程,在該工程內(nèi)創(chuàng)建apt moudle.需要注意,AbstractProcessor是在javax包中,而android 核心庫中不存在該包,因此在選擇創(chuàng)建moudle時需要選擇java Library:

此時項目結(jié)構(gòu)如下:

接下在我們在apt下創(chuàng)建annotation和processor子包,其中annotation用于存放我們自定義的注解,而processor則用于存放我們自定義的注解處理器.

先來個簡單的,自定義@Print注解:該注解最終的作用是輸出被注解的元素:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})  @Retention(RetentionPolicy.CLASS)                                  public @interface Print {                                     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

接下來為其編寫注解處理器:

public class PrintProcessor extends AbstractProcessor {    private Messager mMessager;    @Override    public synchronized void init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        mMessager = processingEnvironment.getMessager();    }    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        for (TypeElement te : annotations) {            for (Element e : roundEnv.getElementsAnnotatedWith(te)) {//find special annotationed element                print(e.toString());//print element            }        }        return true;    }    @Override    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }    @Override    public Set<String> getSupportedAnnotationTypes() {        LinkedHashSet<String> annotations = new LinkedHashSet<>();        annotations.add(Print.class.getCanonicalName());        return super.getSupportedAnnotationTypes();    }    private void print(String msg) {        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

現(xiàn)在我們完成了一個簡單的注解.在編譯階段,編譯器將會輸出被注解元素的信息.由于我們是在Gradle環(huán)境下,因此該信息將在Gradle Console下輸出.

接下來我們編寫一個稍微難點的注解@Code:該注解會生成一個指定格式的類,先看看該注解的定義:

@Retention(CLASS)@Target(METHOD)public @interface Code {    public String author();    public String date() default "";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

接下來,我們需要為其編寫注解處理器,代碼比較簡單,直接來看:

public class CodeProcessor extends AbstractProcessor {    private final String SUFFIX = "$WrmRequestInfo";    private Messager mMessager;    private Filer mFiler;    private Types mTypeUtils;    @Override    public synchronized void init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        mMessager = processingEnvironment.getMessager();        mFiler = processingEnvironment.getFiler();        mTypeUtils = processingEnvironment.getTypeUtils();    }    @Override    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }    @Override    public Set<String> getSupportedAnnotationTypes() {        LinkedHashSet<String> annotations = new LinkedHashSet<>();        annotations.add(Code.class.getCanonicalName());        return annotations;    }    @Override    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        for (Element e : roundEnvironment.getElementsAnnotatedWith(Code.class)) {//find special annotationed element            Code ca = e.getAnnotation(Code.class);            TypeElement clazz = (TypeElement) e.getEnclosingElement();            try {                generateCode(e, ca, clazz);            } catch (IOException x) {                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,                        x.toString());                return false            }        }        return true;    }    //generate     private void generateCode(Element e, Code ca, TypeElement clazz) throws IOException {        JavaFileObject f = mFiler.createSourceFile(clazz.getQualifiedName() + SUFFIX);        mMessager.printMessage(Diagnostic.Kind.NOTE, "Creating " + f.toUri());        Writer w = f.openWriter();        try {            String pack = clazz.getQualifiedName().toString();            PrintWriter pw = new PrintWriter(w);            pw.println("package " + pack.substring(0, pack.lastIndexOf('.')) + ";"); //create package element            pw.println("\n class " + clazz.getSimpleName() + "Autogenerate {");//create class element            pw.println("\n    protected " + clazz.getSimpleName() + "Autogenerate() {}");//create class construction            pw.println("    protected final void message() {");//create method            pw.println("\n//" + e);            pw.println("http://" + ca);            pw.println("\n        System.out.println(\"author:" + ca.author() + "\");");            pw.println("\n        System.out.println(\"date:" + ca.date() + "\");");            pw.println("    }");            pw.println("}");            pw.flush();        } finally {            w.close();        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

核心內(nèi)容在generateCode()方法中,該方法利用上面我們提到的Filer來寫出源文件.你會發(fā)現(xiàn),這里主要就是字符創(chuàng)拼接類的過程嘛,真是太麻煩了.

到現(xiàn)在為止,我們已經(jīng)編寫好了兩個注解及其對應的處理器.現(xiàn)在我們僅需要對其進行配置.

在resources資源文件夾下創(chuàng)建META-INF.services,然后在該路徑下創(chuàng)建名為javax.annotation.processing.Processor的文件,在該文件中配置需要啟用的注解處理器,即寫上處理器的完整路徑,有幾個處理器就寫幾個,分行寫幺,比如我們這里是:

com.closedevice.processor.PrintProcessorcom.closedevice.processor.CodeProcessor
  • 1
  • 2
  • 1
  • 2

到現(xiàn)在我們已經(jīng)做好打包之前的準備了,此時項目結(jié)構(gòu)如下:

下面就需要將apt moudle打成jar包.無論你是在什么平臺上,最終打出jar包就算成功一半了.為了方便演示,直接可視化操作:

來看一下apt.jar的結(jié)構(gòu):

接下來將apt.jar文件復制到主moudle app下的libs文件夾中,開始使用它.我們簡單的在MainActivity.java中使用一下:

public class MainActivity extends AppCompatActivity {    @Override    @Print    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        process();    }    @Code(author = "closedevice",date="20161225")    private void process() {    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

分別在onCreate()和process()方法中使用我們的注解,現(xiàn)在編譯app模塊,在編譯過程中你可以在Gradle Console看到輸出的信息,不出意外的話,你講看到一下信息:

另外在app moudle的build/intermediates/classes/debug/com/closedevice/annotationtest就可以看到自動生成的MainActivityAutogenerate.class了.當然你也可以直接查看編譯階段生成的源碼文件com/closedevice/annotationtest/MainActivity$WrmRequestInfo.java

再來看看自動生成的源代碼:

package com.closedevice.annotationtest; class MainActivityAutogenerate {    protected MainActivityAutogenerate() {}    protected final void message() {//process()//@com.closedevice.annotation.Code(date=20161225, author=closedevice)        System.out.println("author:closedevice");        System.out.println("date:20161225");    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

將該工程部署到我們的模擬器上,不出意外,會看到以下日志信息:

就這樣,一個簡單的編譯時注解處理器就實現(xiàn)了.上面我們利用運行時注解處理器來做了個簡單的ButterKnife,但真正ButterKnife是利用編譯是利用APT來實現(xiàn)的,限于篇幅,這一小節(jié)就不做演示了


總結(jié)

本文初步介紹了運行時注解處理器和編譯時注解處理器,但是有關(guān)APT的內(nèi)容絕非一文可以說明白的,我將在后面逐步介紹有關(guān)APT的相關(guān)知識.

示例Demo在這

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入理解spring中的各種注解
Java注解處理器
java注解
Java Annotation詳解
java Annotation
java annotation(注解)
更多類似文章 >>
生活服務
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服