亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

注解 Annotation

2019-11-10 19:55:33
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

原文鏈接: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)

元數(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ù).

什么是注解(Annotation)?

注解是從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

這里寫(xiě)圖片描述

借助@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ò)誤提示: 這里寫(xiě)圖片描述

@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ì)直接提示,而像第二種則不是直接提示: 這里寫(xiě)圖片描述

但是在編譯過(guò)程中,編譯器都會(huì)警告:

這里寫(xiě)圖片描述

需要注意@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è)元素:<root>,<child>,<subchild>,到現(xiàn)在你已經(jīng)明白元素是什么。對(duì)于java源文件來(lái)說(shuō),他同樣是一種結(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為T(mén)ypeElement }}123456789101112131415123456789101112131415

對(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è)大概的了解: 這里寫(xiě)圖片描述

元素含義
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)中最重要的是getKind()方法,該方法返回TypeKind類(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; } }}123456789101112131415161718192021123456789101112131415161718192021

簡(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);}123456789101112131415161718123456789101112131415161718
Filer

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: 這里寫(xiě)圖片描述

此時(shí)項(xiàng)目結(jié)構(gòu)如下: 這里寫(xiě)圖片描述

接下在我們?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)如下: 這里寫(xiě)圖片描述

下面就需要將apt moudle打成jar包.無(wú)論你是在什么平臺(tái)上,最終打出jar包就算成功一半了.為了方便演示,直接可視化操作: 這里寫(xiě)圖片描述

來(lái)看一下apt.jar的結(jié)構(gòu): 這里寫(xiě)圖片描述

接下來(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à),你講看到一下信息: 這里寫(xiě)圖片描述

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

這里寫(xiě)圖片描述

再來(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ì)看到以下日志信息: 這里寫(xiě)圖片描述

就這樣,一個(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在這


發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
日本免费在线精品| 大菠萝精品导航| 天天操天天操天天色天天要| 精品久久一区二区三区| 中文字幕+乱码+中文乱码91| 美国av免费观看| 草草草视频在线观看| 多男操一女视频| 午夜精品久久久久久久久久蜜桃| 夜色77av精品影院| 无码专区aaaaaa免费视频| 黄毛片在线观看| caoporn国产精品免费视频| 中文字幕亚洲一区二区av在线| 欧美在线观看在线观看| 亚洲一区在线视频观看| 成人黄色免费观看| 天天躁日日躁狠狠躁超碰2020| 3d动漫精品啪啪一区二区竹菊| 中文字幕视频在线免费| 女子免费在线观看视频www| 三级全黄做爰视频| 成人网页在线观看| 久久国产主播精品| 欧美r级在线| 天堂在线中文| 天天操,天天操| 激情在线观看视频| 欧美婷婷久久五月精品三区| 亚洲激情图片qvod| 人人狠狠综合久久亚洲婷| 国产永久免费高清在线观看视频| 欧美性生交大片免费| 中文字幕日韩三级| 欧美激情视频在线| 激情综合激情五月| 无码av免费精品一区二区三区| 国产精品一区二区亚洲| 美女毛片一区二区三区四区最新中文字幕亚洲| 欧美一区二区视频在线观看2022| 久久aⅴ国产紧身牛仔裤| 理论视频在线| 亚洲欧美成人一区二区三区| 久久精品国产亚洲AV无码男同| 国产欧美日韩精品丝袜高跟鞋| 香蕉久久久久久久av网站| 中文字幕一区不卡| 久久国产精品偷| 日本美女黄色一级片| 欧洲一级在线观看| 中文字幕在线中文字幕在线中三区| 国产网站在线看| 亚洲欧美国产精品桃花| 加勒比海盗1在线观看免费国语版| 草色在线视频| 日本欧美肥老太交大片| 成人在线视频一区| 91精品综合久久久久久五月天| 成人国产精品免费视频| 韩国av永久免费| 日本网站在线播放| 久久黄色网页| av一区二区三| 91免费看片| 国产亚洲精品日韩| 中文字幕第二区| 一区二区乱码| 国产高清一区在线观看| 久久久国产精品一区二区三区| 精品国产一区一区二区三亚瑟| 永久看片925tv| 色在线视频网| 久久伊人资源站| 欧美一区二区在线| 国产精品高潮粉嫩av| 高清无码一区二区在线观看吞精| 电影午夜精品一区二区三区| jizzjizzjizzjizzjizzjizzjizz| 亚洲国产精品精华液网站| 粉嫩嫩av羞羞动漫久久久| 国产一级伦理片| 中文另类视频| 9.1人成人免费视频网站| www欧美com| 求av网址在线观看| 欧美日韩国产高清视频| 加勒比免费视频| 色帝国亚洲欧美在线| 亚洲日本在线观看视频| 欧美影视资讯| 日韩免费毛片视频| 一区二区国产精品视频| 小视频福利在线| 亚洲人久久久| y97精品国产97久久久久久| 日韩资源av在线| 国产精品无码专区av在线播放| 国产一区二区久久精品| 亚洲免费av观看| 欧美成人精品h版在线观看| 国产99久久久久久免费看| 91精品婷婷国产综合久久蝌蚪| 国产免费一区二区三区在线能观看| 摸bbb搡bbb搡bbbb| 视频免费一区| 精品88久久久久88久久久| ass大特写| 欧美黑人经典片免费观看| 在线免费日韩av| 日韩 欧美一区二区三区| 国产在线一二区| 免费看日本毛片| 成人影视在线播放| gay视频丨vk| 国产精品天美传媒| 国产免费一区二区三区视频| 久久久久久女乱国产| 一本色道婷婷久久欧美| 主播大秀视频在线观看一区二区| 日本电影一区二区| 在线免费日韩片| 亚洲成a人片在线www| 国产美女高潮在线观看| 日韩中文字幕一区二区高清99| 国产精品视频中文字幕91| 91啦中文在线| 亚洲一区色图| 涩涩视频在线播放| 高清在线成人网| 91九色蝌蚪91por成人| 在线观看国产网站| 女人色偷偷aa久久天堂| 欧美日韩网址| 国产精品美女久久久久久久| 91精品国产高清久久久久久91| 91成人福利在线观看| 国产精品丝袜久久久久久消防器材| 国产剧情一区在线| www.操.com| 性欧美大战久久久久久久| 日本综合在线观看| 久久久久久久久久久免费精品| 亚洲精品成人自拍| 91视频在线视频| 国产亚洲精品美女久久久久| 国产成人精品免费视| 老女人av在线| 色999日韩国产欧美一区二区| 两个人看的无遮挡免费视频| 女同视频在线观看| 国产人成网在线播放va免费| 激情亚洲影院在线观看| 久久久青草婷婷精品综合日韩| 欧美成aaa人片在线观看蜜臀| 欧美在线观看不卡| 国产精品亚洲综合色区韩国| 久久精品国产一区二区三区| 欧美日韩国产一区二区三区不卡| 四虎一区二区三区| 欧美理论影院| 亚洲免费一区二区| 99精品国产高清一区二区麻豆| 欧美精品精品一区| 极品白嫩的小少妇| a级片国产精品自在拍在线播放| 亚洲精品国产第一综合99久久| 黄色的电影在线-骚虎影院-骚虎视频| 天堂а在线中文在线无限看推荐| 99国产精品欲| 欧美国产日韩精品免费观看| 亚洲成人福利视频| 久久久久久久久久久一区| 天堂网中文字幕| 国产欧美一区二区三区四区| av伦理在线| 日韩成人中文字幕| 91福利区在线观看| 日韩一级片大全| 中文不卡在线| 国产精品久久久久久久久鸭| xfplay5566色资源网站| 法国伦理少妇愉情| 亚洲激情社区| 黑人巨大精品欧美一区| 九色综合婷婷综合| 丰满的亚洲女人毛茸茸| 国产网站一区二区| 奇米影视第四色777| 在线视频不卡一区二区三区| 亚洲视频一区| 91精品福利观看| 999精品在线视频| 久久亚洲私人国产精品va| 欧美另类69xxx| 欧美人与性囗牲恔配| 久久精品在线| 18精品爽视频在线观看| 欧美三级蜜桃2在线观看| 99se视频在线观看| 欧美偷拍一区二区三区| 97视频在线观看成人| 亚洲成人免费在线| 免费看成年人视频在线观看| 日韩av综合在线| 中文字幕国产一区二区| 国产精品久久三区| 91小视频在线播放| 欧美剧在线免费观看网站| 国产av无码专区亚洲av毛网站| 色多多视频在线观看| 男女做爰猛烈刺激| 琪琪亚洲精品午夜在线| 2020国产成人综合网| 毛片一区二区三区| 久久久久久久久毛片| 国产剧情一区二区| 在线国产成人影院| 国产综合视频| 337人体粉嫩噜噜噜| 中文字幕一区二区5566日韩| av一区二区三区在线观看| 欧美亚洲另类制服自拍| 色噜噜狠狠狠综合欧洲色8| 91影院在线播放| 一区二区欧美国产| 激情久久99| 欧美大片拔萝卜| 香蕉加勒比综合久久| 成人久久一区二区| 日韩a**中文字幕| 欧美 日韩 国产 一区| 欧美精品一区二区蜜臀亚洲| 精品视频全国免费看| 天天干天天操天天拍| 99国产精品久久久久老师| 久久视频在线播放| 中文字幕av一区二区三区高| 日韩在线观看| 色婷婷亚洲十月十月色天| 精品欧美一区二区三区免费观看| 性一交一乱一区二区洋洋av| 91青娱乐在线视频| 偷窥自拍亚洲色图精选| 亚洲免费看黄网站| 国产在线观看h| 亚洲成色最大综合在线| 日韩美一区二区三区| 日韩欧美中文字幕精品| 99久久免费看精品国产一区| 欧美亚洲综合色| 亚洲精品视频99| 色欲AV无码精品一区二区久久| 中文字幕不卡在线观看| а√天堂8资源在线| 成年人晚上看的视频| 免费亚洲网站| 色综合视频一区二区三区44| 日韩av黄色网址| 偷窥韩漫第三季| 亚州视频一区二区三区| 精品视频在线你懂得| 你懂的在线视频观看| 一区二区三区在线视频观看58| 日韩av不卡电影| 欧美性感美女h网站在线观看免费| xxx国产hd| avtt香蕉久久| 女同毛片一区二区三区| wwww在线观看| 成人午夜影视| 日韩精品极品视频在线观看免费| 日韩一区二区免费在线观看| 久久精品国产第一区二区三区最新章节| 97精品国产99久久久久久免费| 99re在线播放| 精品调教chinesegay| 国产一区二区三区精品在线| 中文字幕日韩有码| 欧美色图亚洲图片| 国产亚洲欧洲高清一区| 小香蕉视频在线| 国产粉嫩一区二区三区在线观看| 99久久伊人精品影院| 黄色成人在线| 91美女高潮出水| 久久99久久亚洲国产| 中文字幕日韩一区二区三区不卡| 国产日韩欧美中文在线播放| 写真片福利在线播放| 欧美色图天堂网| 亚洲黄色av女优在线观看| 日本xxxx高清色视频| 国产丝袜视频在线观看| 在线精品视频一区二区| 国产精一品亚洲二区在线视频| 日本人妻丰满熟妇久久久久久| aa一级黄色片| 久久久久久久久久久久久国产| 91在线视频官网| 欧美激情在线一区二区| 久久艹这里只有精品| 亚洲毛片网站| 五月激情丁香一区二区三区| 亚洲不卡在线播放| 91亚洲精品视频在线观看| 久久久欧美一区二区| 日韩在线中文字幕视频| 亚洲欧洲在线免费| 在线免费精品视频| 日韩一区二区在线免费观看| 亚洲欧洲三级电影| 亚洲成年人影院在线| 欧美sm极限捆绑bd| 91社区视频在线观看| 欧美变态口味重另类| а√天堂官网中文在线| 亚洲综合中文字幕在线观看| 天天综合网久久| 一本色道88久久加勒比精品| 亚洲国产乱码最新视频| 久久久久久久久久久久久女国产乱| 亚洲免费专区| 一本久久a久久精品vr综合| 在线观看自拍| 欧美曰成人黄网| 欧美日韩在线免费观看| 日韩在线观看中文字幕| 青青草偷拍视频|