原文鏈接:http://blog.csdn.net/dd864140130
今天我們就來(lái)細(xì)細(xì)評(píng)味java當(dāng)中Annotation,也就是我們常說(shuō)的注解.
本文按照以下順序進(jìn)行:元數(shù)據(jù)->元注解->運(yùn)行時(shí)注解->編譯時(shí)注解處理器.
元數(shù)據(jù)由metadata譯來(lái),所謂的元數(shù)據(jù)就是“關(guān)于數(shù)據(jù)的數(shù)據(jù)”,更通俗的說(shuō)就是描述數(shù)據(jù)的數(shù)據(jù),對(duì)數(shù)據(jù)及信息資源的描述性信息.比如說(shuō)一個(gè)文本文件,有創(chuàng)建時(shí)間,創(chuàng)建人,文件大小等數(shù)據(jù),這都可以理解為是元數(shù)據(jù).
在java中,元數(shù)據(jù)以標(biāo)簽的形式存在java代碼中,它的存在并不影響程序代碼的編譯和執(zhí)行,通常它被用來(lái)生成其它的文件或運(yùn)行時(shí)知道被運(yùn)行代碼的描述信息。java當(dāng)中的javadoc和注解都屬于元數(shù)據(jù).
注解是從Java 5.0開(kāi)始加入,可以用于標(biāo)注包,類(lèi),方法,變量等.比如我們常見(jiàn)的@Override,再或者Android源碼中的@hide,@systemApi,@PRivateApi等
對(duì)于@Override,多數(shù)人往往都是知其然而不知其所以然,今天我就來(lái)聊聊Annotation背后的秘密,開(kāi)始正文.
元注解就是定義注解的注解,是java提供給我們用于定義注解的基本注解.在java.lang.annotation包中我們可以看到目前元注解共有以下幾個(gè):
@Retention@Target@Inherited@Documented@interface下面我們將集合@Override注解來(lái)解釋著5個(gè)基本注解的用法.
@interface
@interface是java中用于聲明注解類(lèi)的關(guān)鍵字.使用該注解表示將自動(dòng)繼承java.lang.annotation.Annotation類(lèi),該過(guò)程交給編譯器完成.
因此我們想要定義一個(gè)注解只需要如下做即可,以@Override注解為例
public @interface Override {}123123需要注意:在定義注解時(shí),不能繼承其他注解或接口.
@Retention
@Retention:該注解用于定義注解保留策略,即定義的注解類(lèi)在什么時(shí)候存在(源碼階段 or 編譯后 or 運(yùn)行階段).該注解接受以下幾個(gè)參數(shù):
RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME,其具體使用及含義如下:
注解保留策略 含義 @Retention(RetentionPolicy.SOURCE) 注解僅在源碼中保留,class文件中不存在 @Retention(RetentionPolicy.CLASS) 注解在源碼和class文件中都存在,但運(yùn)行時(shí)不存在,即運(yùn)行時(shí)無(wú)法獲得,該策略也是默認(rèn)的保留策略 @Retention(RetentionPolicy.RUNTIME) 注解在源碼,class文件中存在且運(yùn)行時(shí)可以通過(guò)反射機(jī)制獲取到 來(lái)看一下@Override注解的保留策略:
@Retention(RetentionPolicy.SOURCE)public @interface Override {}12341234這表明@Override注解只在源碼階段存在,javac在編譯過(guò)程中去去掉該注解.
@Target
該注解用于定義注解的作用目標(biāo),即注解可以用在什么地方,比如是用于方法上還是用于字段上,該注解接受以下參數(shù):
作用目標(biāo) 含義 @Target(ElementType.TYPE) 用于接口(注解本質(zhì)上也是接口),類(lèi),枚舉 @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為例,不難看出其作用目標(biāo)為方法:
@Target(ElementType.METHOD)public @interface Override {}12341234到現(xiàn)在,通過(guò)@interface,@Retention,@Target已經(jīng)可以完整的定義一個(gè)注解,來(lái)看@Override完整定義:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}123456123456@Inherited
默認(rèn)情況下,我們自定義的注解用在父類(lèi)上不會(huì)被子類(lèi)所繼承.如果想讓子類(lèi)也繼承父類(lèi)的注解,即注解在子類(lèi)也生效,需要在自定義注解時(shí)設(shè)置@Inherited.一般情況下該注解用的比較少.
@Documented
該注解用于描述其它類(lèi)型的annotation應(yīng)該被javadoc文檔化,出現(xiàn)在api doc中. 比如使用該注解的@Target會(huì)出出現(xiàn)在api說(shuō)明中.
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target { ElementType[] value();}1234567812345678
借助@Interface,@Target,@Retention,@Inherited,@Documented這五個(gè)元注解,我們就可以自定義注解了,其中前三個(gè)注解是任何一個(gè)注解都必備具備的.
你以為下面會(huì)直接來(lái)將如何自定義注解嘛?不,你錯(cuò)了,我們還是來(lái)聊聊java自帶的幾個(gè)注解.
系統(tǒng)注解
java設(shè)計(jì)者已經(jīng)為我們自定義了幾個(gè)常用的注解,我們稱(chēng)之為系統(tǒng)注解,主要是這三個(gè):
系統(tǒng)注解 含義 @Override 用于修飾方法,表示此方法重寫(xiě)了父類(lèi)方法 @Deprecated 用于修飾方法,表示此方法已經(jīng)過(guò)時(shí) @SuppressWarnnings 該注解用于告訴編譯器忽視某類(lèi)編譯警告 如果你已經(jīng)完全知道這三者的用途,跳過(guò)這一小節(jié),直接往下看.
@Override
它用作標(biāo)注方法,說(shuō)明被標(biāo)注的方法重寫(xiě)了父類(lèi)的方法,其功能類(lèi)似斷言.如果在一個(gè)沒(méi)有重寫(xiě)父類(lèi)方法的方法上使用該注解,java編譯器將會(huì)以一個(gè)編譯錯(cuò)誤提示:
@Deprecated
當(dāng)某個(gè)類(lèi)型或者成員使用該注解時(shí)意味著 編譯器不推薦開(kāi)發(fā)者使用被標(biāo)記的元素.另外,該注解具有”傳遞性”,子類(lèi)中重寫(xiě)該注解標(biāo)記的方法,盡管子類(lèi)中的該方法未使用該注解,但編譯器仍然報(bào)警.
public class SimpleCalculator { @Deprecated public int add(int x, int y) { return x+y; }}public class MultiplCalculator extends SimpleCalculator { // 重寫(xiě)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); }}123456789101112131415161718192021222324123456789101112131415161718192021222324對(duì)于像new SimpleCalculator().add(3,4)這種直接調(diào)用的,Idea會(huì)直接提示,而像第二種則不是直接提示:
但是在編譯過(guò)程中,編譯器都會(huì)警告:
需要注意@Deprecated和@deprecated這兩者的區(qū)別,前者被javac識(shí)別和處理,而后者則是被javadoc工具識(shí)別和處理.因此當(dāng)我們需要在源碼標(biāo)記某個(gè)方法已經(jīng)過(guò)時(shí)應(yīng)該使用@Deprecated,如果需要在文檔中說(shuō)明則使用@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; }}1234567891011121314151617181912345678910111213141516171819@SuppressWarnning
該注解被用于有選擇的關(guān)閉編譯器對(duì)類(lèi),方法,成員變量即變量初始化的警告.該注解可接受以下參數(shù):
參數(shù) 含義 deprecated 使用已過(guò)時(shí)類(lèi),方法,變量 unchecked 執(zhí)行了未檢查的轉(zhuǎn)告時(shí)的警告,如使用集合是為使用泛型來(lái)制定集合保存時(shí)的類(lèi)型 fallthrough 使用switch,但是沒(méi)有break時(shí) path 類(lèi)路徑,源文件路徑等有不存在的路徑 serial 可序列化的類(lèi)上缺少serialVersionUID定義時(shí)的警告 finally 任何finally字句不能正常完成時(shí)的警告 all 以上所有情況的警告 滋溜一下,我們飛過(guò)了2016年,不,是看完了上一節(jié).繼續(xù)往下飛.
自定義注解
了解完系統(tǒng)注解之后,現(xiàn)在我們就可以自己來(lái)定義注解了,通過(guò)上面@Override的實(shí)例,不難看出定義注解的格式如下:
public @interface 注解名 {定義體}11定義體就是方法的集合,每個(gè)方法實(shí)則是聲明了一個(gè)配置參數(shù).方法的名稱(chēng)作為配置參數(shù)的名稱(chēng),方法的返回值類(lèi)型就是配置參數(shù)的類(lèi)型.和普通的方法不一樣,可以通過(guò)default關(guān)鍵字來(lái)聲明配置參數(shù)的默認(rèn)值.
需要注意:
此處只能使用public或者默認(rèn)的defalt兩個(gè)權(quán)限修飾符配置參數(shù)的類(lèi)型只能使用基本類(lèi)型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation對(duì)于只含有一個(gè)配置參數(shù)的注解,參數(shù)名建議設(shè)置中value,即方法名為value配置參數(shù)一旦設(shè)置,其參數(shù)值必須有確定的值,要不在使用注解的時(shí)候指定,要不在定義注解的時(shí)候使用default為其設(shè)置默認(rèn)值,對(duì)于非基本類(lèi)型的參數(shù)值來(lái)說(shuō),其不能為null.像@Override這樣,沒(méi)有成員定義的注解稱(chēng)之為標(biāo)記注解.
現(xiàn)在我們來(lái)自定義個(gè)注解@UserMeta,這個(gè)注解目前并沒(méi)啥用,就是為了演示一番:
@Documented@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)public @interface UserMeta { public int id() default 0; public String name() default ""; public int age() default ;}12345678910111234567891011有了米飯,沒(méi)有筷子沒(méi)法吃啊(手抓飯的走開(kāi)),下面來(lái)看看如何處理注解.
注解處理器
上面我們已經(jīng)學(xué)會(huì)了如何定義注解,要想注解發(fā)揮實(shí)際作用,需要我們?yōu)樽⒔饩帉?xiě)相應(yīng)的注解處理器.根據(jù)注解的特性,注解處理器可以分為運(yùn)行時(shí)注解處理和編譯時(shí)注解處理器.運(yùn)行時(shí)處理器需要借助反射機(jī)制實(shí)現(xiàn),而編譯時(shí)處理器則需要借助APT來(lái)實(shí)現(xiàn).
無(wú)論是運(yùn)行時(shí)注解處理器還是編譯時(shí)注解處理器,主要工作都是讀取注解及處理特定注解,從這個(gè)角度來(lái)看注解處理器還是非常容易理解的.
先來(lái)看看如何編寫(xiě)運(yùn)行時(shí)注解處理器.
運(yùn)行時(shí)注解處理器
熟悉java反射機(jī)制的同學(xué)一定對(duì)java.lang.reflect包非常熟悉,該包中的所有api都支持讀取運(yùn)行時(shí)Annotation的能力,即屬性為@Retention(RetentionPolicy.RUNTIME)的注解.
在java.lang.reflect中的AnnotatedElement接口是所有程序元素的(Class,Method)父接口,我們可以通過(guò)反射獲取到某個(gè)類(lèi)的AnnotatedElement對(duì)象,進(jìn)而可以通過(guò)該對(duì)象提供的方法訪問(wèn)Annotation信息,常用的方法如下:
方法 含義 <T extends Annotation> T getAnnotation(Class<T> annotationClass)返回該元素上存在的制定類(lèi)型的注解 Annotation[] getAnnotations()返回該元素上存在的所有注解 default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)返回該元素指定類(lèi)型的注解 default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)返回直接存在與該元素上的所有注釋 default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)返回直接存在該元素岸上某類(lèi)型的注釋 Annotation[] getDeclaredAnnotations()返回直接存在與該元素上的所有注釋 編寫(xiě)運(yùn)行時(shí)注解大體就需要了解以上知識(shí)點(diǎn),下面來(lái)做個(gè)小實(shí)驗(yàn).
簡(jiǎn)單示例
首先我們用一個(gè)簡(jiǎn)單的實(shí)例來(lái)介紹如何編寫(xiě)運(yùn)行時(shí)注解處理器:我們的系統(tǒng)中存在一個(gè)User實(shí)體類(lèi):
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 + '/'' + '}'; }}1234567891011121314151617181920212223242526272812345678910111213141516171819202122232425262728我們希望可以通過(guò)
@UserMeta(id=1,name="dong",age = 10)(這個(gè)注解我們?cè)谏厦嫣岬搅?來(lái)為設(shè)置User實(shí)例的默認(rèn)值。自定義注解類(lèi)如下:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.CONSTRUCTOR)public @interface UserMeta { public int id() default 0; public String name() default ""; public int age() default 0;}1234567891012345678910該注解類(lèi)作用于構(gòu)造方法,并在運(yùn)行時(shí)存在,這樣我們就可以在運(yùn)行時(shí)通過(guò)反射獲取注解進(jìn)而為User實(shí)例設(shè)值,看看如何處理該注解吧.
運(yùn)行時(shí)注解處理器:
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); } } }}1234567891011121314151617181920212223242512345678910111213141516171819202122232425測(cè)試代碼:
public class Main { public static void main(String[] args) { User user = new User(); AnnotationProcessor.init(user); System.out.println(user.toString()); }}1234567891012345678910運(yùn)行測(cè)試代碼,便得到我們想要的結(jié)果:
User{id=1, age=10, name=’dong’}
這里通過(guò)反射獲取User類(lèi)聲明的構(gòu)造方法,并檢測(cè)是否使用了@UserMeta注解。然后從注解中獲取參數(shù)值并將其賦值給User對(duì)象。
正如上面提到,運(yùn)行時(shí)注解處理器的編寫(xiě)本質(zhì)上就是通過(guò)反射獲取注解信息,隨后進(jìn)行其他操作。編譯一個(gè)運(yùn)行時(shí)注解處理器就是這么簡(jiǎn)單。運(yùn)行時(shí)注解通常多用于參數(shù)配置類(lèi)模塊。
自己動(dòng)手編寫(xiě)B(tài)utterKnife
對(duì)從事Android開(kāi)發(fā)的小伙伴而言,ButterKnife可謂是神兵利器,能極大的減少我們書(shū)寫(xiě)
findViewById(XXX).現(xiàn)在,我們就利用剛才所學(xué)的運(yùn)行時(shí)注解處理器來(lái)編寫(xiě)一個(gè)簡(jiǎn)化版的ButterKnife。自定義注解:
//該注解用于配置layout資源@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentView { int value();//只有一個(gè)返回時(shí),可用value做名稱(chēng),這樣在使用的時(shí)候就不需要使用的名稱(chēng)進(jìn)行標(biāo)志}//該注解用于配置控件ID@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject { int id(); boolean clickable() default false;}1234567891011121314151612345678910111213141516自定義運(yùn)行時(shí)注解:
public class ButterKnife { //view控件 public static void initViews(Object object, View sourceView){ //獲取該類(lèi)聲明的成員變量 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()); }}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849501234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950測(cè)試代碼:
@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; } }}1234567891011121314151617181920212223242526272829303132333435363712345678910111213141516171819202122232425262728293031323334353637一個(gè)簡(jiǎn)單的ButterKnife就實(shí)現(xiàn)了,是不是非常簡(jiǎn)單。下面我們就進(jìn)入本文的最重要的一點(diǎn):編譯時(shí)注解處理器。
編譯時(shí)注解處理器
不同于運(yùn)行時(shí)注解處理器,編寫(xiě)編譯時(shí)注解處理器(Annotation Processor Tool).
APT用于在編譯時(shí)期掃描和處理注解信息.一個(gè)特定的注解處理器可以以java源碼文件或編譯后的class文件作為輸入,然后輸出另一些文件,可以是.java文件,也可以是.class文件,但通常我們輸出的是.java文件.(注意:并不是對(duì)源文件修改).如果輸出的是.java文件,這些.java文件回合其他源碼文件一起被javac編譯.
你可能很納悶,注解處理器是到底是在什么階段介入的呢?好吧,其實(shí)是在javac開(kāi)始編譯之前,這也就是通常我們?yōu)槭裁丛敢廨敵?java文件的原因.
注解最早是在java 5引入,主要包含apt和com.sum.mirror包中相關(guān)mirror api,此時(shí)apt和javac是各自獨(dú)立的。從java 6開(kāi)始,注解處理器正式標(biāo)準(zhǔn)化,apt工具也被直接集成在javac當(dāng)中。
我們還是回到如何編寫(xiě)編譯時(shí)注解處理器這個(gè)話(huà)題上,編譯一個(gè)編譯時(shí)注解處理主要分兩步:
繼承AbstractProcessor,實(shí)現(xiàn)自己的注解處理器注冊(cè)處理器,并打成jar包看起來(lái)很簡(jiǎn)單不是么?來(lái)慢慢的看看相關(guān)的知識(shí)點(diǎn)吧.
自定義注解處理器
首先來(lái)看一下一個(gè)標(biāo)準(zhǔ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; }}123456789101112131415161718192021222324123456789101112131415161718192021222324來(lái)簡(jiǎn)單的了解下其中5個(gè)方法的作用
方法 作用 init(ProcessingEnvironment processingEnv) 該方法有注解處理器自動(dòng)調(diào)用,其中ProcessingEnvironment類(lèi)提供了很多有用的工具類(lèi):Filter,Types,Elements,Messager等 getSupportedAnnotationTypes() 該方法返回字符串的集合表示該處理器用于處理那些注解 getSupportedSourceVersion() 該方法用來(lái)指定支持的java版本,一般來(lái)說(shuō)我們都是支持到最新版本,因此直接返回 SourceVersion.latestSupported()即可process(Set annotations, RoundEnvironment roundEnv) 該方法是注解處理器處理注解的主要地方,我們需要在這里寫(xiě)掃描和處理注解的代碼,以及最終生成的java文件。其中需要深入的是RoundEnvironment類(lèi),該用于查找出程序元素上使用的注解 編寫(xiě)一個(gè)注解處理器首先要對(duì)ProcessingEnvironment和RoundEnvironment非常熟悉。接下來(lái)我們一覽這兩個(gè)類(lèi)的風(fēng)采.首先來(lái)看一下ProcessingEnvironment類(lèi):
public interface ProcessingEnvironment { Map<String,String> getOptions(); //Messager用來(lái)報(bào)告錯(cuò)誤,警告和其他提示信息 Messager getMessager(); //Filter用來(lái)創(chuàng)建新的源文件,class文件以及輔助文件 Filer getFiler(); //Elements中包含用于操作Element的工具方法 Elements getElementUtils(); //Types中包含用于操作TypeMirror的工具方法 Types getTypeUtils(); SourceVersion getSourceVersion(); Locale getLocale();}123456789101112131415161718192021123456789101112131415161718192021重點(diǎn)來(lái)認(rèn)識(shí)一下Element,Types和Filer。Element(元素)是什么呢?
Element
element表示一個(gè)靜態(tài)的,語(yǔ)言級(jí)別的構(gòu)件。而任何一個(gè)結(jié)構(gòu)化文檔都可以看作是由不同的element組成的結(jié)構(gòu)體,比如xml,JSON等。這里我們用XML來(lái)示例:
<root> <child> <subchild>.....</subchild> </child></root>1234512345這段xml中包含了三個(gè)元素:
package com.closedevice; //PackageElementpublic class Main{ //TypeElement private int x; //VariableElement private Main(){ //ExecuteableElement } private void print(String msg){ //其中的參數(shù)部分String msg為T(mén)ypeElement }}123456789101112131415123456789101112131415<root>,<child>,<subchild>,到現(xiàn)在你已經(jīng)明白元素是什么。對(duì)于java源文件來(lái)說(shuō),他同樣是一種結(jié)構(gòu)化文檔:對(duì)于java源文件來(lái)說(shuō),Element代表程序元素:包,類(lèi),方法都是一種程序元素。另外如果你對(duì)網(wǎng)頁(yè)解析工具jsoup熟悉,你會(huì)覺(jué)得操作此處的element是非常容易,關(guān)于jsoup不在本文講解之內(nèi)。
接下來(lái)看看看各種Element之間的關(guān)系圖圖,以便有個(gè)大概的了解:
元素 含義 VariableElement 代表一個(gè) 字段, 枚舉常量, 方法或者構(gòu)造方法的參數(shù), 局部變量及 異常參數(shù)等元素 PackageElement 代表包元素 TypeElement 代表類(lèi)或接口元素 ExecutableElement 代碼方法,構(gòu)造函數(shù),類(lèi)或接口的初始化代碼塊等元素,也包括注解類(lèi)型元素 TypeMirror
這三個(gè)類(lèi)也需要我們重點(diǎn)掌握: DeclaredType代表聲明類(lèi)型:類(lèi)類(lèi)型還是接口類(lèi)型,當(dāng)然也包括參數(shù)化類(lèi)型,比如
Set<String>,也包括原始類(lèi)型TypeElement代表類(lèi)或接口元素,而DeclaredType代表類(lèi)類(lèi)型或接口類(lèi)型。
TypeMirror代表java語(yǔ)言中的類(lèi)型.Types包括基本類(lèi)型,聲明類(lèi)型(類(lèi)類(lèi)型和接口類(lèi)型),數(shù)組,類(lèi)型變量和空類(lèi)型。也代表通配類(lèi)型參數(shù),可執(zhí)行文件的簽名和返回類(lèi)型等。TypeMirror類(lèi)中最重要的是
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; } }}123456789101112131415161718192021123456789101112131415161718192021getKind()方法,該方法返回TypeKind類(lèi)型,為了方便大家理解,這里附上其源碼:簡(jiǎn)單來(lái)說(shuō),Element代表源代碼,TypeElement代表的是源碼中的類(lèi)型元素,比如類(lèi)。雖然我們可以從TypeElement中獲取類(lèi)名,TypeElement中不包含類(lèi)本身的信息,比如它的父類(lèi),要想獲取這信息需要借助TypeMirror,可以通過(guò)Element中的
asType()獲取元素對(duì)應(yīng)的TypeMirror。然后來(lái)看一下RoundEnvironment,這個(gè)類(lèi)比較簡(jiǎn)單,一筆帶過(guò):
public interface RoundEnvironment { boolean processingOver(); //上一輪注解處理器是否產(chǎn)生錯(cuò)誤 boolean errorRaised(); //返回上一輪注解處理器生成的根元素 Set<? extends Element> getRootElements(); //返回包含指定注解類(lèi)型的元素的集合 Set<? extends Element> getElementsAnnotatedWith(TypeElement a); //返回包含指定注解類(lèi)型的元素的集合 Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);}123456789101112131415161718123456789101112131415161718Filer
Filer用于注解處理器中創(chuàng)建新文件。具體用法在下面示例會(huì)做演示.另外由于Filer用起來(lái)實(shí)在比較麻煩,后面我們會(huì)使用javapoet簡(jiǎn)化我們的操作.
好了,關(guān)于AbstractProcessor中一些重要的知識(shí)點(diǎn)我們已經(jīng)看完了.假設(shè)你現(xiàn)在已經(jīng)編寫(xiě)完一個(gè)注解處理器了,下面,要做什么呢?
打包并注冊(cè).
自定義的處理器如何才能生效呢?為了讓java編譯器或能夠找到自定義的注解處理器我們需要對(duì)其進(jìn)行注冊(cè)和打包:自定義的處理器需要被打成一個(gè)jar,并且需要在jar包的META-INF/services路徑下中創(chuàng)建一個(gè)固定的文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中需要填寫(xiě)自定義處理器的完整路徑名,有幾個(gè)處理器就需要填寫(xiě)幾個(gè)。
從java 6之后,我們只需要將打出的jar防止到項(xiàng)目的buildpath下即可,javac在運(yùn)行的過(guò)程會(huì)自動(dòng)檢查javax.annotation.processing.Processor注冊(cè)的注解處理器,并將其注冊(cè)上。而java 5需要單獨(dú)使用apt工具,java 5想必用的比較少了,就略過(guò)吧.
到現(xiàn)在為止,已經(jīng)大體的介紹了與注解處理器相關(guān)的一些概念,最終我們需要獲得是一個(gè)包含注解處理器代碼的jar包.
接下來(lái),來(lái)實(shí)踐一把.
簡(jiǎn)單實(shí)例
用個(gè)簡(jiǎn)單的示例,來(lái)演示如何在Gradle來(lái)創(chuàng)建一個(gè)編譯時(shí)注解處理器,為了方便起見(jiàn),這里就直接借助Android studio.當(dāng)然你也可以采用maven構(gòu)建.
首先創(chuàng)建AnnotationTest工程,在該工程內(nèi)創(chuàng)建apt moudle.需要注意,AbstractProcessor是在javax包中,而android 核心庫(kù)中不存在該包,因此在選擇創(chuàng)建moudle時(shí)需要選擇java Library:
此時(shí)項(xiàng)目結(jié)構(gòu)如下:
接下在我們?cè)赼pt下創(chuàng)建annotation和processor子包,其中annotation用于存放我們自定義的注解,而processor則用于存放我們自定義的注解處理器.
先來(lái)個(gè)簡(jiǎn)單的,自定義@Print注解:該注解最終的作用是輸出被注解的元素:
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.CLASS) public @interface Print { }1234512345接下來(lái)為其編寫(xiě)注解處理器:
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); }}1234567891011121314151617181920212223242526272829303132333435363712345678910111213141516171819202122232425262728293031323334353637現(xiàn)在我們完成了一個(gè)簡(jiǎn)單的注解.在編譯階段,編譯器將會(huì)輸出被注解元素的信息.由于我們是在Gradle環(huán)境下,因此該信息將在Gradle Console下輸出.
接下來(lái)我們編寫(xiě)一個(gè)稍微難點(diǎn)的注解@Code:該注解會(huì)生成一個(gè)指定格式的類(lèi),先看看該注解的定義:
@Retention(CLASS)@Target(METHOD)public @interface Code { public String author(); public String date() default "";}12345671234567接下來(lái),我們需要為其編寫(xiě)注解處理器,代碼比較簡(jiǎn)單,直接來(lái)看:
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("//" + 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(); } }}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970711234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071核心內(nèi)容在
generateCode()方法中,該方法利用上面我們提到的Filer來(lái)寫(xiě)出源文件.你會(huì)發(fā)現(xiàn),這里主要就是字符創(chuàng)拼接類(lèi)的過(guò)程嘛,真是太麻煩了.到現(xiàn)在為止,我們已經(jīng)編寫(xiě)好了兩個(gè)注解及其對(duì)應(yīng)的處理器.現(xiàn)在我們僅需要對(duì)其進(jìn)行配置.
在resources資源文件夾下創(chuàng)建META-INF.services,然后在該路徑下創(chuàng)建名為javax.annotation.processing.Processor的文件,在該文件中配置需要啟用的注解處理器,即寫(xiě)上處理器的完整路徑,有幾個(gè)處理器就寫(xiě)幾個(gè),分行寫(xiě)幺,比如我們這里是:
com.closedevice.processor.PrintProcessorcom.closedevice.processor.CodeProcessor1212到現(xiàn)在我們已經(jīng)做好打包之前的準(zhǔn)備了,此時(shí)項(xiàng)目結(jié)構(gòu)如下:
下面就需要將apt moudle打成jar包.無(wú)論你是在什么平臺(tái)上,最終打出jar包就算成功一半了.為了方便演示,直接可視化操作:
來(lái)看一下apt.jar的結(jié)構(gòu):
接下來(lái)將apt.jar文件復(fù)制到主moudle app下的libs文件夾中,開(kāi)始使用它.我們簡(jiǎn)單的在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() { }}123456789101112131415161718123456789101112131415161718分別在onCreate()和process()方法中使用我們的注解,現(xiàn)在編譯app模塊,在編譯過(guò)程中你可以在Gradle Console看到輸出的信息,不出意外的話(huà),你講看到一下信息:
另外在app moudle的
build/intermediates/classes/debug/com/closedevice/annotationtest就可以看到自動(dòng)生成的MainActivityAutogenerate.class了.當(dāng)然你也可以直接查看編譯階段生成的源碼文件com/closedevice/annotationtest/MainActivity$WrmRequestInfo.java
再來(lái)看看自動(dòng)生成的源代碼:
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"); }}1234567891011121314151612345678910111213141516將該工程部署到我們的模擬器上,不出意外,會(huì)看到以下日志信息:
就這樣,一個(gè)簡(jiǎn)單的編譯時(shí)注解處理器就實(shí)現(xiàn)了.上面我們利用運(yùn)行時(shí)注解處理器來(lái)做了個(gè)簡(jiǎn)單的ButterKnife,但真正ButterKnife是利用編譯是利用APT來(lái)實(shí)現(xiàn)的,限于篇幅,這一小節(jié)就不做演示了
總結(jié)
本文初步介紹了運(yùn)行時(shí)注解處理器和編譯時(shí)注解處理器,但是有關(guān)APT的內(nèi)容絕非一文可以說(shuō)明白的,我將在后面逐步介紹有關(guān)APT的相關(guān)知識(shí).
示例Demo在這
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注