<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    dyerac  
    dyerac In Java
    公告

    日歷
    <2007年7月>
    24252627282930
    1234567
    891011121314
    15161718192021
    22232425262728
    2930311234
    統計
    • 隨筆 - 36
    • 文章 - 10
    • 評論 - 94
    • 引用 - 0

    導航

    常用鏈接

    留言簿(5)

    隨筆分類(49)

    隨筆檔案(36)

    文章分類(11)

    文章檔案(10)

    相冊

    dyerac

    搜索

    •  

    積分與排名

    • 積分 - 79220
    • 排名 - 705

    最新隨筆

    最新評論

    閱讀排行榜

    評論排行榜

     

    基礎學習教程:Java Annotation入門

    摘要:

     本文針對java初學者或者annotation初次使用者全面地說明了annotation的使 用方法、定義方式、分類。初學者可以通過以上的說明制作簡單的annotation程序,但是對于一些高級的annotation應用(例如使用自定義 annotation生成javabean映射xml文件)還需要進一步的研究和探討。涉及到深入annotation的內容,作者將在后文《Java Annotation高級應用》中談到。

     同時,annotation運行存在兩種方式:運行時、編譯時。上文中討論的都是在運行時的annotation應用,但在編譯時的annotation應用還沒有涉及,

     一、為什么使用Annotation:

     在JAVA應用中,我們常遇到一些需要使用模版代碼。例如,為了編寫一個JAX-RPC web service,我們必須提供一對接口和實現作為模版代碼。如果使用annotation對遠程訪問的方法代碼進行修飾的話,這個模版就能夠使用工具自動生成。

     另外,一些API需要使用與程序代碼同時維護的附屬文件。例如,JavaBeans需要一個 BeanInfo Class與一個Bean同時使用/維護,而EJB則同樣需要一個部署描述符。此時在程序中使用annotation來維護這些附屬文件的信息將十分便利 而且減少了錯誤。

     二、Annotation工作方式:

     在5.0版之前的Java平臺已經具有了一些ad hoc annotation機制。比如,使用transient修飾符來標識一個成員變量在序列化子系統中應被忽略。而@deprecated這個 javadoc tag也是一個ad hoc annotation用來說明一個方法已過時。從Java5.0版發布以來,5.0平臺提供了一個正式的annotation功能:允許開發者定義、使用 自己的annoatation類型。此功能由一個定義annotation類型的語法和一個描述annotation聲明的語法,讀取annotaion 的API,一個使用annotation修飾的class文件,一個annotation處理工具(apt)組成。

     annotation并不直接影響代碼語義,但是它能夠工作的方式被看作類似程序的工具或者類庫,它會反過來對正在運行的程序語義有所影響。annotation可以從源文件、class文件或者以在運行時反射的多種方式被讀取。

     當然annotation在某種程度上使javadoc tag更加完整。一般情況下,如果這個標記對java文檔產生影響或者用于生成java文檔的話,它應該作為一個javadoc tag;否則將作為一個annotation.

     三、Annotation使用方法:

     1.類型聲明方式:

     通常,應用程序并不是必須定義annotation類型,但是定義annotation類型并非難事。Annotation類型聲明于一般的接口聲明極為類似,區別只在于它在interface關鍵字前面使用"@"符號。

     annotation類型的每個方法聲明定義了一個annotation類型成員,但方法聲明不必 有參數或者異常聲明;方法返回值的類型被限制在以下的范圍:primitives、String、Class、enums、annotation和前面類 型的數組;方法可以有默認值。

     下面是一個簡單的annotation類型聲明:

     清單1:

     

        /**
         * Describes the Request-For-Enhancement(RFE) that led
         * to the presence of the annotated API element.
         */
        public @interface RequestForEnhancement {
            int    id();
            String synopsis();
            String engineer() default "[unassigned]";
            String date();    default "[unimplemented]";
        }

     代碼中只定義了一個annotation類型RequestForEnhancement.

     2.修飾方法的annotation聲明方式:

     annotation 是一種修飾符,能夠如其它修飾符(如public、static、final)一般使用。習慣用法是annotaions用在其它的修飾符前面。 annotations由"@+annotation類型+帶有括號的成員-值列表"組成。這些成員的值必須是編譯時常量(即在運行時不變)。

     A:下面是一個使用了RequestForEnhancement annotation的方法聲明:

     清單2:

        @RequestForEnhancement(
            id       = 2868724,
            synopsis = "Enable time-travel",
            engineer = "Mr. Peabody",
            date     = "4/1/3007"
        )
        public static void travelThroughTime(Date destination) { ... }

     B:當聲明一個沒有成員的annotation類型聲明時,可使用以下方式:


       清單3:

        /**
         * Indicates that the specification of the annotated API element
         * is preliminary and subject to change.
         */
        public @interface Preliminary { }

     
     作為上面沒有成員的annotation類型聲明的簡寫方式:

     

     清單4:



     

        @Preliminary public class TimeTravel { ... }


     C:如果在annotations中只有唯一一個成員,則該成員應命名為value:

     

     清單5:



     

        /**
         * Associates a copyright notice with the annotated API element.
         */
        public @interface Copyright {
            String value();
        }


     更為方便的是對于具有唯一成員且成員名為value的annotation(如上文),在其使用時可以忽略掉成員名和賦值號(=):

     

     清單6:


     

        @Copyright("2002 Yoyodyne Propulsion Systems")
        public class OscillationOverthruster { ... }


     3.一個使用實例:

     

     結合上面所講的,我們在這里建立一個簡單的基于annotation測試框架。首先我們需要一個annotation類型來表示某個方法是一個應該被測試工具運行的測試方法。

     

     清單7:


     

        import java.lang.annotation.*;

        /**
         * Indicates that the annotated method is a test method.
         * This annotation should be used only on parameterless static methods.
         */
        @Retention(RetentionPolicy.RUNTIME)
        @Target(ElementType.METHOD)
        public @interface Test { }


     值得注意的是annotaion類型聲明是可以標注自己的,這樣的annotation被稱為“meta-annotations”。

     

    在上面的代碼中,@Retention(RetentionPolicy.RUNTIME)這個meta-annotation表示了此類型的 annotation將被虛擬機保留使其能夠在運行時通過反射被讀取。而@Target(ElementType.METHOD)表示此類型的 annotation只能用于修飾方法聲明。

     

     下面是一個簡單的程序,其中部分方法被上面的annotation所標注:

     

     清單8:


     

        public class Foo {
            @Test public static void m1() { }
            public static void m2() { }
            @Test public static void m3() {
                throw new RuntimeException("Boom");
            }
            public static void m4() { }
            @Test public static void m5() { }
            public static void m6() { }
            @Test public static void m7() {
                throw new RuntimeException("Crash");
            }
            public static void m8() { }
        }

    Here is the testing tool:

        import java.lang.reflect.*;

        public class RunTests {
           public static void main(String[] args) throws Exception {
              int passed = 0, failed = 0;
              for (Method m : Class.forName(args[0]).getMethods()) {
                 if (m.isAnnotationPresent(Test.class)) {
                    try {
                       m.invoke(null);
                       passed++;
                    } catch (Throwable ex) {
                       System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                       failed++;
                    }
                 }
              }
              System.out.printf("Passed: %d, Failed %d%n", passed, failed);
           }
        }


    這個程序從命令行參數中取出類名,并且遍歷此類的所有方法,嘗試調用其中被上面的測試annotation類型標注過的方法。在此過程中為了找出哪些方法 被annotation類型標注過,需要使用反射的方式執行此查詢。如果在調用方法時拋出異常,此方法被認為已經失敗,并打印一個失敗報告。最后,打印運 行通過/失敗的方法數量。

     

     下面文字表示了如何運行這個基于annotation的測試工具:

     

     清單9:



     

        $ java RunTests Foo
        Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
        Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
        Passed: 2, Failed 2


     四、Annotation分類:

     

     根據annotation的使用方法和用途主要分為以下幾類:

     

     1.內建Annotation――Java5.0版在java語法中經常用到的內建Annotation:

     

     @Deprecated用于修飾已經過時的方法;

     

     @Override用于修飾此方法覆蓋了父類的方法(而非重載);

     

     @SuppressWarnings用于通知java編譯器禁止特定的編譯警告。

     

     下面代碼展示了內建Annotation類型的用法:

     

     清單10:



     

    package com.bjinfotech.practice.annotation;

    /**
     *
    演示如何使用java5內建的annotation
     *
    參考資料:

     * http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
     * http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
     * http://mindprod.com/jgloss/annotations.html
     * @author cleverpig
     *
     */
    import java.util.List;

    public class UsingBuiltInAnnotation {
            //
    食物類
            class Food{}
            //
    干草類
            class Hay extends Food{}
            //
    動物類
            class Animal{
                    Food getFood(){
                            return null;
                    }
                    //
    使用Annotation聲明Deprecated方法
                    @Deprecated
                    void deprecatedMethod(){
                    }
            }
            //
    馬類-繼承動物類
            class Horse extends Animal{
                    //
    使用Annotation聲明覆蓋方法
                    @Override
                    Hay getFood(){
                            return new Hay();
                    }
                    //
    使用Annotation聲明禁止警告
                    @SuppressWarnings({"deprecation","unchecked"})
                    void callDeprecatedMethod(List horseGroup){
                            Animal an=new Animal();
                            an.deprecatedMethod();
                            horseGroup.add(an);
                    }
            }
    }


     2.開發者自定義Annotation:由開發者自定義Annotation類型。

     

     下面是一個使用annotation進行方法測試的sample:

     

     AnnotationDefineForTestFunction類型定義如下:

     

     清單11:

     

    package com.bjinfotech.practice.annotation;

    import java.lang.annotation.*;
    /**
     *
    定義annotation
     * @author cleverpig
     *
     */
    //
    加載在VM中,在運行時進行映射

    @Retention(RetentionPolicy.RUNTIME)
    //
    限定此annotation只能標示方法
    @Target(ElementType.METHOD)
    public @interface AnnotationDefineForTestFunction{}


     測試annotation的代碼如下:

     

     清單12:



    Java Annotation 高級應用

    作者:cleverpig





    版權聲明:本文可以自由轉載,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
    作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
    原文:http://www.matrix.org.cn/resource/article/44/44062_Java+Annotation+Apt.html
    關鍵字:java,annotation,apt

    前言:
    前不久在matrix上先后發表了《java annotation 入門》《java annotation 手冊》兩 篇文章,比較全面的對java annotation的語法、原理、使用三方面進行了闡述。由于《入門》中的簡單例程雖然簡單明了的說明了annotation用法,但給大家的感覺可能 是意猶未見,所以在此行文《java annotation高級應用》,具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。

    一、摘要:
    《java annotation高級應用》具體實例化解釋annotation和annotation processing tool(APT)的使用。望能對各位的有所幫助。本文列舉了用于演示annotation的BRFW演示框架、演示APT的apt代碼實例,并對其進行 較為深度的分析,希望大家多多提意見。

    二、annotation實例分析
    1.BRFW(Beaninfo Runtime FrameWork)定義:
    本人編寫的一個annotation功能演示框架。顧名思義,BRFW就是在運行時取得bean信息的框架。

    2.BRFW的功能:
    A.源代碼級annotation:在bean的源代碼中使用annotation定義bean的信息;
    B.運行時獲取bean數據:在運行時分析bean class中的annotation,并將當前bean class中field信息取出,功能類似xdoclet;
    C.運行時bean數據的xml綁定:將獲得的bean數據構造為xml文件格式展現。熟悉j2ee的朋友知道,這個功能類似jaxb。

    3.BRFW框架:
    BRFW主要包含以下幾個類:
    A.Persistent類:定義了用于修飾類的固有類型成員變量的annotation。
    B.Exportable類:定義了用于修飾Class的類型的annotation。
    C.ExportToXml類:核心類,用于完成BRFW的主要功能:將具有Exportable Annotation的bean對象轉換為xml格式文本。
    D.AddressForTest類:被A和B修飾過的用于測試目的的地址bean類。其中包含了地址定義所必需的信息:國家、省級、城市、街道、門牌等。
    E.AddressListForTest類: 被A和B修飾過的友人通訊錄bean類。其中包含了通訊錄所必備的信息:友人姓名、年齡、電話、住址(成員為AddressForTest類型的 ArrayList)、備注。需要說明的是電話這個bean成員變量是由字符串類型組成的ArrayList類型。由于朋友的住址可能不唯一,故這里的住 址為由AddressForTest類型組成的ArrayList。
    從上面的列表中,可以發現A、B用于修飾bean類和其類成員;C主要用于取出bean類的數據并將其作xml綁定,代碼中使用了E作為測試類;E中可能包含著多個D。
    在了解了這個簡單框架后,我們來看一下BRFW的代碼吧!

    4.BRFW源代碼分析:
    A.Persistent類:
    清單1:

    package com.bjinfotech.practice.annotation.runtimeframework;

    import java.lang.annotation.*;

    /**
    * 用于修飾類的固有類型成員變量的annotation
    * @author cleverpig
    *
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Persistent {
            String value() default "";
    }


    B.Exportable類:
    清單2:

    package com.bjinfotech.practice.annotation.runtimeframework;

    import java.lang.annotation.*;

    /**
    * 用于修飾類的類型的annotation
    * @author cleverpig
    *
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Exportable {
            //名稱
            String name() default "";
            //描述
            String description() default "";
            //省略name和description后,用來保存name值
            String value() default "";
            
    }


    C.AddressForTest類:
    清單3:

    package com.bjinfotech.practice.annotation.runtimeframework;

    /**
    * 用于測試的地址類
    * @author cleverpig
    *
    */
    @Exportable("address")
    public class AddressForTest {
            //國家
            @Persistent
            private String country=null;
            
            //省級
            @Persistent
            private String province=null;
            
            //城市
            @Persistent
            private String city=null;
            
            //街道
            @Persistent
            private String street=null;

            //門牌
            @Persistent
            private String doorplate=null;
            
            public AddressForTest(String country,String province,
                            String city,String street,String doorplate){
                    this.country=country;
                    this.province=province;
                    this.city=city;
                    this.street=street;
                    this.doorplate=doorplate;
            }
            
    }


    D.AddressListForTest類:
    清單4:

    package com.bjinfotech.practice.annotation.runtimeframework;

    import java.util.*;

    /**
    * 友人通訊錄
    * 包含:姓名、年齡、電話、住址(多個)、備注
    * @author cleverpig
    *
    */
    @Exportable(name="addresslist",description="address list")
    public class AddressListForTest {
            //友人姓名
            @Persistent
            private String friendName=null;
            
            //友人年齡
            @Persistent
            private int age=0;
            
            //友人電話
            @Persistent
            private ArrayList<String> telephone=null;
            
            //友人住址:家庭、單位
            @Persistent
            private ArrayList<AddressForTest> AddressForText=null;
            
            //備注
            @Persistent
            private String note=null;
            
            public AddressListForTest(String name,int age,
                            ArrayList<String> telephoneList,
                            ArrayList<AddressForTest> addressList,
                            String note){
                    this.friendName=name;
                    this.age=age;
                    this.telephone=new ArrayList<String>(telephoneList);
                    this.AddressForText=new ArrayList<AddressForTest>(addressList);
                    this.note=note;
                    
            }
    }


    E.ExportToXml類:
    清單5:

    package com.bjinfotech.practice.annotation.runtimeframework;

    import java.lang.reflect.Field;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.ArrayList;

    /**
    * 將具有Exportable Annotation的對象轉換為xml格式文本
    * @author cleverpig
    *
    */
    public class ExportToXml {
            /**
             * 返回對象的成員變量的值(字符串類型)
             * @param field 對象的成員變量
             * @param fieldTypeClass 對象的類型
             * @param obj 對象
             * @return 對象的成員變量的值(字符串類型)
             */
            private String getFieldValue(Field field,Class fieldTypeClass,Object obj){
                    String value=null;
                    
                    try{
                            if (fieldTypeClass==String.class){
                                    value=(String)field.get(obj);
                            }
                            else if (fieldTypeClass==int.class){
                                    value=Integer.toString(field.getInt(obj));
                            }
                            else if (fieldTypeClass==long.class){
                                    value=Long.toString(field.getLong(obj));
                            }
                            else if (fieldTypeClass==short.class){
                                    value=Short.toString(field.getShort(obj));
                            }
                            else if (fieldTypeClass==float.class){
                                    value=Float.toString(field.getFloat(obj));
                            }
                            else if (fieldTypeClass==double.class){
                                    value=Double.toString(field.getDouble(obj));
                            }
                            else if (fieldTypeClass==byte.class){
                                    value=Byte.toString(field.getByte(obj));
                            }
                            else if (fieldTypeClass==char.class){
                                    value=Character.toString(field.getChar(obj));
                            }
                            else if (fieldTypeClass==boolean.class){
                                    value=Boolean.toString(field.getBoolean(obj));
                            }
                    }
                    catch(Exception ex){
                            ex.printStackTrace();
                            value=null;
                    }
                    return value;
            }
            
            /**
             * 輸出對象的字段,當對象的字段為Collection或者Map類型時,要調用exportObject方法繼續處理
             * @param obj 被處理的對象
             * @throws Exception
             */
            public void exportFields(Object obj) throws Exception{
                    Exportable exportable=obj.getClass().getAnnotation(Exportable.class);        
                    if (exportable!=null){
                            if (exportable.value().length()>0){
    //                                System.out.println("Class annotation Name:"+exportable.value());
                            }
                            else{
    //                                System.out.println("Class annotation Name:"+exportable.name());
                            }
                    }
                    else{
    //                        System.out.println(obj.getClass()+"類不是使用Exportable標注過的");
                    }
                    
                    //取出對象的成員變量
                    Field[] fields=obj.getClass().getDeclaredFields();
                    
                    for(Field field:fields){
                            //獲得成員變量的標注
                            Persistent fieldAnnotation=field.getAnnotation(Persistent.class);
                            if (fieldAnnotation==null){
                                    continue;
                            }
                            //重要:避免java虛擬機檢查對私有成員的訪問權限
                            field.setAccessible(true);
                            Class typeClass=field.getType();
                            String name=field.getName();
                            String value=getFieldValue(field,typeClass,obj);
                            
                            //如果獲得成員變量的值,則輸出
                            if (value!=null){
                                    System.out.println(getIndent()+"<"+name+">\n"
                                                    +getIndent()+"\t"+value+"\n"+getIndent()+"</"+name+">");
                            }
                            //處理成員變量中類型為Collection或Map
                            else if ((field.get(obj) instanceof Collection)||
                                            (field.get(obj) instanceof Map)){
                                    exportObject(field.get(obj));
                            }
                            else{
                                    exportObject(field.get(obj));
                            }
                            
                    }
            }
            
            //縮進深度
            int levelDepth=0;
            //防止循環引用的檢查者,循環引用現象如:a包含b,而b又包含a
            Collection<Object> cyclicChecker=new ArrayList<Object>();
            
            /**
             * 返回縮進字符串
             * @return
             */
            private String getIndent(){
                    String s="";
                    for(int i=0;i<levelDepth;i++){
                            s+="\t";
                    }
                    return s;
            }
            /**
             * 輸出對象,如果對象類型為Collection和Map類型,則需要遞歸調用exportObject進行處理
             * @param obj
             * @throws Exception
             */
            public void exportObject(Object obj) throws Exception{
                    Exportable exportable=null;
                    String elementName=null;
                    
                    //循環引用現象處理
                    if (cyclicChecker.contains(obj)){
                            return;
                    }
                    
                    cyclicChecker.add(obj);
                    
                    //首先處理Collection和Map類型
                    if (obj instanceof Collection){
                            for(Iterator i=((Collection)obj).iterator();i.hasNext();){
                                    exportObject(i.next());
                            }
                    }
                    else if (obj instanceof Map){
                            for(Iterator i=((Map)obj).keySet().iterator();i.hasNext();){
                                    exportObject(i.next());
                            }
                    }
                    else{

                            exportable=obj.getClass().getAnnotation(Exportable.class);
                            //如果obj已經被Exportable Annotation修飾過了(注意annotation是具有繼承性的),
                            //則使用其name作為輸出xml的元素name
                            if (exportable!=null){
                                    if (exportable.value().length()>0){
                                            elementName=exportable.value();
                                    }
                                    else{
                                            elementName=exportable.name();
                                    }
                            }
                            //未被修飾或者Exportable Annotation的值為空字符串,
                            //則使用類名作為輸出xml的元素name
                            if (exportable==null||elementName.length()==0){
                                    elementName=obj.getClass().getSimpleName();
                            }
                            //輸出xml元素頭
                            System.out.println(getIndent()+"<"+elementName+">");
                            levelDepth++;
                            //如果沒有被修飾,則直接輸出其toString()作為元素值
                            if (exportable==null){
                                    System.out.println(getIndent()+obj.toString());
                            }
                            //否則將對象的成員變量導出為xml
                            else{
                                    exportFields(obj);
                            }
                            levelDepth--;
                            //輸出xml元素結尾
                            System.out.println(getIndent()+"</"+elementName+">");
                            
                    }
                    cyclicChecker.remove(obj);
            }
            
            public static void main(String[] argv){
                    try{
                            AddressForTest ad=new AddressForTest("China","Beijing",
                                            "Beijing","winnerStreet","10");
                            
                            ExportToXml test=new ExportToXml();
                            
                            ArrayList<String> telephoneList=new ArrayList<String>();
                            telephoneList.add("66608888");
                            telephoneList.add("66608889");
                            
                            ArrayList<AddressForTest> adList=new ArrayList<AddressForTest>();
                            adList.add(ad);
                            
                            AddressListForTest adl=new AddressListForTest("coolBoy",
                                            18,telephoneList,adList,"some words");
                            
                            test.exportObject(adl);
                    }
                    catch(Exception ex){
                            ex.printStackTrace();
                    }
            }
    }


    在ExportToXml類之前的類比較簡單,這里必須說明一下ExportToXml類:此類的核心函數是exportObject和 exportFields方法,前者輸出對象的xml信息,后者輸出對象成員變量的信息。由于對象類型和成員類型的多樣性,所以采取了以下的邏輯:

    在exportObject方法中,當對象類型為Collection和Map類型時,則需要遞歸調用exportObject進行處理;
    而如果對象類型不是Collection和Map類型的話,將判斷對象類是否被Exportable annotation修飾過:
    如果沒有被修飾,則直接輸出<對象類名>對象.toString()</對象類名>作為xml綁定結果的一部分;
    如果被修飾過,則需要調用exportFields方法對對象的成員變量進行xml綁定。

    在exportFields 方法中,首先取出對象的所有成員,然后獲得被Persisitent annotation修飾的成員。在其后的一句:field.setAccessible(true)是很重要的,因為bean類定義中的成員訪問修飾都 是private,所以為了避免java虛擬機檢查對私有成員的訪問權限,加上這一句是必需的。接著后面的語句便是輸出<成員名>成員值 </成員名>這樣的xml結構。像在exportObject方法中一般,仍然需要判斷成員類型是否為Collection和Map類型,如 果為上述兩種類型之一,則要在exportFields中再次調用exportObject來處理這個成員。

    在main方法中,本人編寫了一段演示代碼:建立了一個由單個友人地址類(AddressForTest)組成的ArrayList作為通訊錄類(AddressForTest)的成員的通訊錄對象,并且輸出這個對象的xml綁定,運行結果如下:

    清單6:

    <addresslist>
            <friendName>
                    coolBoy
            </friendName>
            <age>
                    18
            </age>
            <String>
                    66608888
            </String>
            <String>
                    66608889
            </String>
            <address>
                    <country>
                            China
                    </country>
                    <province>
                            Beijing
                    </province>
                    <city>
                            Beijing
                    </city>
                    <street>
                            winnerStreet
                    </street>
                    <doorplate>
                            10
                    </doorplate>
            </address>
            <note>
                    some words
            </note>
    </addresslist>


    三、APT實例分析:
    1.何謂APT?
    根 據sun官方的解釋,APT(annotation processing tool)是一個命令行工具,它對源代碼文件進行檢測找出其中的annotation后,使用annotation processors來處理annotation。而annotation processors使用了一套反射API并具備對JSR175規范的支持。
    annotation processors處理annotation的基本過程如下:首先,APT運行annotation processors根據提供的源文件中的annotation生成源代碼文件和其它的文件(文件具體內容由annotation processors的編寫者決定),接著APT將生成的源代碼文件和提供的源文件進行編譯生成類文件。
    簡單的和前面所講的annotation 實例BRFW相比,APT就像一個在編譯時處理annotation的javac。而且從sun開發者的blog中看到,java1.6 beta版中已將APT的功能寫入到了javac中,這樣只要執行帶有特定參數的javac就能達到APT的功能。

    2.為何使用APT?
    使 用APT主要目的是簡化開發者的工作量,因為APT可以在編譯程序源代碼的同時,生成一些附屬文件(比如源文件、類文件、程序發布描述文字等),這些附屬 文件的內容也都是與源代碼相關的。換句話說,使用APT就是代替了傳統的對代碼信息和附屬文件的維護工作。使用過hibernate或者beehive等 軟件的朋友可能深有體會。APT可以在編譯生成代碼類的同時將相關的文件寫好,比如在使用beehive時,在代碼中使用annotation聲明了許多 struct要用到的配置信息,而在編譯后,這些信息會被APT以struct配置文件的方式存放。

    3.如何定義processor?
    A.APT工作過程:
    從 整個過程來講,首先APT檢測在源代碼文件中哪些annotation存在。然后APT將查找我們編寫的annotation processor factories類,并且要求factories類提供處理源文件中所涉及的annotation的annotation processor。接下來,一個合適的annotation processors將被執行,如果在processors生成源代碼文件時,該文件中含有annotation,則APT將重復上面的過程直到沒有新文 件生成。

    B.編寫annotation processors:
    編寫一個annotation processors需要使用java1.5 lib目錄中的tools.jar提供的以下4個包:
    com.sun.mirror.apt: 和APT交互的接口;
    com.sun.mirror.declaration: 用于模式化類成員、類方法、類聲明的接口;
    com.sun.mirror.type: 用于模式化源代碼中類型的接口;
    com.sun.mirror.util: 提供了用于處理類型和聲明的一些工具。

    每 個processor實現了在com.sun.mirror.apt包中的AnnotationProcessor接口,這個接口有一個名為 “process”的方法,該方法是在APT調用processor時將被用到的。一個processor可以處理一種或者多種annotation類 型。
    一個processor實例被其相應的工廠返回,此工廠為AnnotationProcessorFactory接口的實現。APT將調用工 廠類的getProcessorFor方法來獲得processor。在調用過程中,APT將提供給工廠類一個 AnnotationProcessorEnvironment 類型的processor環境類對象,在這個環境對象中,processor將找到其執行所需要的每件東西,包括對所操作的程序結構的參考,與APT通訊 并合作一同完成新文件的建立和警告/錯誤信息的傳輸。

    提供工廠類有兩個方式:通過APT的“-factory”命令行參數提供,或者讓工廠類在APT的發現過程中被自動定位(關于發現過程詳細介紹請看http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html)。前者對于一個已知的factory來講是一種主動而又簡單的方式;而后者則是需要在jar文件的META-INF/services目錄中提供一個特定的發現路徑:
    在 包含factory類的jar文件中作以下的操作:在META-INF/services目錄中建立一個名為 com.sun.mirror.apt.AnnotationProcessorFactory 的UTF-8編碼文件,在文件中寫入所有要使用到的factory類全名,每個類為一個單獨行。

    4.一個簡單的APT實例分析:
    A.實例構成:
    Review類:定義Review Annotation;
    ReviewProcessorFactory類:生成ReviewProcessor的工廠類;
    ReviewProcessor類:定義處理Review annotation的Processor;
    ReviewDeclarationVisitor類:定義Review annotation聲明訪問者,ReviewProcessor將要使用之對Class進行訪問。
    runapt.bat:定義了使用自定義的ReviewProcessor對Review類源代碼文件進行處理的APT命令行。

    B.Review類:
    清單7:

    package com.bjinfotech.practice.annotation.apt;

    /**
    * 定義Review Annotation
    * @author cleverpig
    *
    */
    public @interface Review {
            public static enum TypeEnum{EXCELLENT,NICE,NORMAL,BAD};
            TypeEnum type();
            String name() default "Review";
    }


    C.ReviewProcessorFactory類:
    清單8:

    package com.bjinfotech.practice.annotation.apt;

    import java.util.Collection;
    import java.util.Set;
    import java.util.Arrays;
    import com.sun.mirror.apt.*;
    import com.sun.mirror.declaration.AnnotationTypeDeclaration;
    import com.sun.mirror.apt.AnnotationProcessorEnvironment;
    //請注意為了方便,使用了靜態import
    import static java.util.Collections.unmodifiableCollection;
    import static java.util.Collections.emptySet;

    /**
    * 生成ReviewProcessor的工廠類
    * @author cleverpig
    *
    */
    public class ReviewProcessorFactory implements AnnotationProcessorFactory{
            /**
             * 獲得針對某個(些)類型聲明定義的Processor
             * @param atds 類型聲明集合
             * @param env processor環境
             */
            public AnnotationProcessor getProcessorFor(
                            Set<AnnotationTypeDeclaration> atds,
                            AnnotationProcessorEnvironment env){
                    return new ReviewProcessor(env);
            }
            /**
             * 定義processor所支持的annotation類型
             * @return processor所支持的annotation類型的集合
             */
            public Collection<String>         supportedAnnotationTypes(){
                    //“*”表示支持所有的annotation類型
                    //當然也可以修改為“foo.bar.*”、“foo.bar.Baz”,來對所支持的類型進行修飾
                return unmodifiableCollection(Arrays.asList("*"));
        }
            
            /**
             * 定義processor支持的選項
             * @return processor支持選項的集合
             */
            public Collection<String>         supportedOptions(){
                    //返回空集合
                return emptySet();
        }
            
            public static void main(String[] argv){
                    System.out.println("ok");
            }
    }


    D.ReviewProcessor類:
    清單9:

    package com.bjinfotech.practice.annotation.apt;

    import com.sun.mirror.apt.AnnotationProcessor;
    import com.sun.mirror.apt.AnnotationProcessorEnvironment;
    import com.sun.mirror.declaration.TypeDeclaration;
    import com.sun.mirror.util.DeclarationVisitors;
    import com.sun.mirror.util.DeclarationVisitor;

    /**
    * 定義Review annotation的Processor
    * @author cleverpig
    *
    */
    public class ReviewProcessor implements AnnotationProcessor{
            //Processor所工作的環境
            AnnotationProcessorEnvironment env=null;
            
            /**
             * 構造方法
             * @param env 傳入processor環境
             */
            public ReviewProcessor(AnnotationProcessorEnvironment env){
                    this.env=env;
            }
            
            /**
             * 處理方法:查詢processor環境中的類型聲明,
             */
            public void process(){
                    //查詢processor環境中的類型聲明
                    for(TypeDeclaration type:env.getSpecifiedTypeDeclarations()){
                            //返回對類進行掃描、訪問其聲明時使用的DeclarationVisitor,
                            //傳入參數:new ReviewDeclarationVisitor(),為掃描開始前進行的對類聲明的處理
                            //        DeclarationVisitors.NO_OP,表示在掃描完成時進行的對類聲明不做任何處理
                            DeclarationVisitor visitor=DeclarationVisitors.getDeclarationScanner(
                                            new ReviewDeclarationVisitor(),DeclarationVisitors.NO_OP);
                            //應用DeclarationVisitor到類型
                            type.accept(visitor);
                    }
            }
    }


    E.ReviewDeclarationVisitor類:
    清單10:

    package com.bjinfotech.practice.annotation.apt;

    import com.sun.mirror.util.*;
    import com.sun.mirror.declaration.*;

    /**
    * 定義Review annotation聲明訪問者
    * @author cleverpig
    *
    */
    public class ReviewDeclarationVisitor extends SimpleDeclarationVisitor{
            /**
             * 定義訪問類聲明的方法:打印類聲明的全名
             * @param cd 類聲明對象
             */
            public void visitClassDeclaration(ClassDeclaration cd){
                    System.out.println("獲取Class聲明:"+cd.getQualifiedName());
            }
            
            public void visitAnnotationTypeDeclaration(AnnotationTypeDeclaration atd){
                    System.out.println("獲取Annotation類型聲明:"+atd.getSimpleName());
            }
            
            public void visitAnnotationTypeElementDeclaration(AnnotationTypeElementDeclaration aed){
                    System.out.println("獲取Annotation類型元素聲明:"+aed.getSimpleName());
            }
    }


    F.runapt.bat文件內容如下:
    清單11:

    E:
    rem 項目根目錄
    set PROJECT_ROOT=E:\eclipse3.1RC3\workspace\tigerFeaturePractice
    rem 包目錄路徑
    set PACKAGEPATH=com\bjinfotech\practice\annotation\apt
    rem 運行根路徑
    set RUN_ROOT=%PROJECT_ROOT%\build
    rem 源文件所在目錄路徑
    set SRC_ROOT=%PROJECT_ROOT%\test
    rem 設置Classpath
    set CLASSPATH=.;%JAVA_HOME%;%JAVA_HOME%/lib/tools.jar;%RUN_ROOT%

    cd %SRC_ROOT%\%PACKAGEPATH%
    apt -nocompile -factory com.bjinfotech.practice.annotation.apt.ReviewProcessorFactory  ./*.java


    四、參考資源:
    http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
    作者的Blog:http://blog.matrix.org.cn/page/cleverpig


    五、源代碼下載:
    [下載文件]



    -------------------------------------------
    1、引入

    l         編程的一個最新趨勢,尤其是 Java 編程,就是使用元數據

    l         元數據可以用于創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查

    l         許多元數據工具(如 Xdoclet)將這些功能添加到核心 Java 語言中,暫時成為 Java 編程功能的一部分

    l         Javadoc是元數據工具,但除了生成文檔之外,沒有固定、實用、標準化的方式將數據用于其他用途,而且HTML代碼經常混入到Javadoc輸出中,更進一步降低了其用于任何其它目的的價值

    l         JSR 175Java編程語言的元數據工具,為將元數據合并到核心 Java 語言中提供了正式理由和說明

    l         Tiger 增加了Annotation的新功能,將一個更通用的元數據工具合并到核心 Java 語言中

    l         Annotation是可以添加到代碼中的修飾符,可以用于包聲明、類型聲明、構造函數、方法、域變量、參數和變量

    l         Tiger包含內置的Annotation,還支持自己編寫的定制Annotation

    l         本部分將概述元數據的優點,并介紹Tiger的內置Annotation

     

    2、元數據的價值

    一般來說,元數據的好處分為三類:文檔編制、編譯器檢查和代碼分析

    1)文檔編制

    l         代碼級文檔最常被引用,但對于將元數據添加到 Java 語言中來說,文檔編制可能是最不相關的理由

    l         因為Javadoc已經提供了非常容易理解和健壯的方法來文檔化代碼

    2)編譯時檢查

    l         元數據更重要的優點是編譯器可以使用它來執行基本的編譯時檢查

    l         具體情況請參看后面介紹的Tiger內置Annotation@Override

    3)代碼分析

    l         元數據工具的最好功能就是可以使用額外數據來分析代碼

    l         簡單的案例就是:許多時候,方法的參數類型或返回類型實際上不是該方法想要的類型;例如,參數類型可能是Object,但方法可能僅使用Integer,這在覆蓋超類的方法時很容易發生;元數據可以指示代碼分析工具:雖然參數類型是 Object,但 Integer 才是真正需要的

    l         復雜的案例就是:即使是簡單EJB系統中也具有很強的依賴性和復雜性,要具有 HomeRemote接口,以及本地的Home Remote接口,以及一個實現類,保持所有這些類同步非常困難;好的工具(如XDoclet)可以管理所有這些依賴性,并確保這些沒有“代碼級”聯系,但有“邏輯級”聯系的類保持同步;元數據在這里確實可以發揮它的作用

     

    3Annotation基礎

    l         Annotation的格式是:@Annotation

    l         Annotation需要數據時,通過name=value的形式提供

    l         代碼中可以用很多Annotation,有些Annotation會具有相同的Annotation類型

    l         Annotation類型和Annotation的概念類似于類和對象的概念

    l         Annotation有三種基本種類:

    Ø         標記Annotation:只有Annotation名,不包含數據,如@MarkerAnnotation

    Ø         單值Annotation:只有單一的數據,可以簡化name=value的形式為value形式,如@SingleValueAnnotation("my data")

    Ø         完整格式的Annotation:有多個數據成員,如@FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")

    l         可以使用花括號向Annotation變量提供值數組,如

    @TODOItems({    // Curly braces indicate an array of values is being supplied
      @TODO(
        severity=TODO.CRITICAL,
        item="Add functionality to calculate the mean of the student's grades",
        assignedTo="Brett McLaughlin"
      ),
      @TODO(
        severity=TODO.IMPOTANT,
        item="Print usage message to screen if no command-line flags specified",
        assignedTo="Brett McLaughlin"
      ),
      @TODO(
        severity=TODO.LOW,
        item="Roll a new website page with this class's new features",
        assignedTo="Jason Hunter"
      )
    })

     

    4Tiger內置Annotation

    1@Override

    l         @Override只用于方法,指明改方法覆蓋超類中的對應方法

    l         簡單例子:

    public class OverrideTester {
     
           public OverrideTester() {
           }
     
           @Override public String toString() {
                  return super.toString() + " [Override Tester Implementation]";
           }
     
           @Override public int hashCode() {
                  return toString().hashCode();
           }
    }

    l         @Override可以檢查輸入錯誤導致無法覆蓋超類方法的問題,例如hashCode()錯誤的輸入為hasCode(),在編譯時就會報錯:

    The method hasCode() of type OverrideTester must override a superclass method

    l         這個便捷的小功能將幫助快速捕獲打字錯誤

    2@Deprecated

    l         同樣只用于方法,指明該方法不應該再使用了

    l         簡單例子:

    public class DeprecatedClass {
     
           @Deprecated public void doSomething() {
                  System.out.println("Deprecated method!");
                  // some code
           }
     
           public void doSomethingElse() {
                  // This method presumably does what doSomething() does, but better
           }
    }

    l         單獨編譯正常通過,如果通過覆蓋或調用Deprecated方法,編譯器會給出警告信息

    l         注:本人在Eclipse 3.1M4環境中測試,根本不起作用(即使是改了編譯參數,why?),在命令行下使用-Xlint:deprecated參數,JAVAC只給出警告信息,編譯還是通過的

    3@SuppressWarnings

    l         Tiger的泛型功能使得編譯器對類型的安全性進行檢查,特別是Java集合,如下面的例子:

           public void nonGenericsMethod() {
                  List wordList = new ArrayList(); // no typing information on the List
                  wordList.add("foo"); // causes error on list addition
           }

    l         編譯器會給出下面的警告信息:

    Type safety: The method add(Object) belongs to the raw type List. References to generic 
     type List<E> should be parameterized

    l         這對于Tiger的代碼是很有幫助的,但對于JDK1.4及以前版本,不斷的收到無關的警告信息是很煩人的

    l         可以使用@SuppressWarnings來阻止指定類型的警告信息,如:

           @SuppressWarnings(value = { "unchecked" }) 
           public void nonGenericsMethod() {
                  List wordList = new ArrayList(); // no typing information on the List
                  wordList.add("foo"); // causes error on list addition
           }

    l         傳遞給@SuppressWarnings的類型值是一個數組,因此可以同時阻止多種類型的警告信息

    l         類型值是由編譯器廠商所指定的,所以上面的例子我在Eclipse 3.1M4環境和命令行中測試,都不起作用,大概是類型值沒有指定對吧


    ---------------------------------------

    1、自定義Annotation類型

    1)定義Annotation類型

    l         使用@interface聲明Annotation類型

    public @interface InProgress {
     
    }

    l         使用Annotation類型

    public class TestAnnotation {
           @InProcess
           public void test() {
             
           }
    }

    l         如果Annotation類型和使用它的類不在相同的包中,可以import Annotation類型,以便直接使用 @InProgress

    2)添加成員

    l         Annotation類型可以有成員變量,以提供有用的信息

    l         定義數據成員不需要定義gettersetter方法,只需要定義一個以成員名稱命名的方法,并指定返回類型為需要的數據類型

    l         簡單的例子:

    public @interface TODO {
           String value();
    }

    l         使用帶成員的Annotation類型:

    public class TestAnnotation {
           @InProcess
           @TODO("Need to finish this method later")
           public void test() {
             
           }
    }

    3)設置缺省值

    l         要為Annotation類型的成員設置缺省值,需要在聲明成員時使用default關鍵字:

    public @interface GroupTODO {
           public enum Severity {
                  CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION
           };
     
           Severity severity() default Severity.IMPORTANT;
           String item();
           String assignedTo();
           String dateAssigned();
    }

    l         當然,缺省值的類型必須與成員變量聲明的類型完全相同

    l         下面是使用缺省值的例子:

    public class TestAnnotation {
           @InProcess
           @GroupTODO(
                  item="Need to finish this method later",
                  assignedTo="nelson_tu",
                  dateAssigned="2005/02/05"
           )
           public void test() {
             
           }
    }

    l         下面是改寫缺省值的例子:

    public class TestAnnotation {
           @InProcess
           //@TODO("Need to finish this method later")
           @GroupTODO(
                  severity=GroupTODO.Severity.DOCUMENTATION,
                  item="Need to finish this method later",
                  assignedTo="nelson_tu",
                  dateAssigned="2005/02/05"
           )
           public void test() {
             
           }
    }

     

    2、元Annotation

    l         Annotation就是AnnotationAnnotationJDK5提供了4種預定義的元Annotation

    1@Target

    l         @Target指定Annotation類型可以應用的程序元素,以便在其它程序元素中誤用Annotation類型

    l         程序元素的類型由java.lang.annotation.ElementType枚舉類定義:

    package java.lang.annotation;
     
    public enum ElementType {
      TYPE,          // Class, interface, or enum (but not annotation)
      FIELD,         // Field (including enumerated values)
      METHOD,        // Method (does not include constructors)
      PARAMETER,             // Method parameter
      CONSTRUCTOR,           // Constructor
      LOCAL_VARIABLE, // Local variable or catch clause
      ANNOTATION_TYPE,       // Annotation Types (meta-annotations)
      PACKAGE        // Java package
    }

    l         下面是使用@Target的例子:

    @Target({ElementType.TYPE,
        ElementType.METHOD,
        ElementType.CONSTRUCTOR,
        ElementType.ANNOTATION_TYPE})
    public @interface TODO {
           String value();
    }

    2@Retention

    l         @Retention Java 編譯器處理Annotation類型的方式有關

    l         這些方式由java.lang.annotation.RetentionPolicy 枚舉類定義:

    package java.lang.annotation;
     
    public enum RetentionPolicy {
      SOURCE,       // Annotation is discarded by the compiler
      CLASS,       // Annotation is stored in the class file, but ignored by the VM
      RUNTIME       // Annotation is stored in the class file and read by the VM
    }

    l         使用@Retention的例子參看后面的@Documented

    3@Documented

    l         @Documented指明需要在Javadoc中包含Annotation(缺省是不包含的)

    l         下面是一個使用@Documented的例子:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InProcess {
     
    }

    l         使用@Documented的一個技巧就是指定保持性策略為RetentionPolicy.RUNTIME:這樣,Annotation就會保留在編譯后的類文件中并且由虛擬機加載,然后Javadoc就可以抽取出Annotation,添加到類的HTML文檔中

    4@Inherited

    l         @Inherited最復雜、使用最少、也最容易造成混淆的一個

    l         假設使用@InProgress 標記一個正在開發的類,只要正確應用@DocumentedAnnotation信息就會出現在Javadoc中;現在要編寫一個新類,擴展那個正在開發的類,那么使用子類,或者查看它的文檔,根本沒法表明還有什么地方沒有完成;而本來是希望@InProgress Annotation信息會被帶到子類中,這就需要使用@Inherited

    l         下面是這樣的例子:

    @Documented
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InProcess {
     
    }

     



    posted on 2007-07-22 22:42 dyerac in java... 閱讀(831) 評論(1)  編輯  收藏 所屬分類: JavaSE
    評論:
    • # 《中國科技博覽》征稿函  劉越 Posted @ 2008-10-24 09:47

      《中國科技博覽》征稿函

      中國核心期刊(遴選)數據庫收錄期刊   中國學術期刊中國(光盤版)收錄期刊
      統一刊號: CN11-4450/T ISSN1009-914X
      (國家級學術期刊 科技論文交流平臺)
      《中國科技博覽》雜志是由國務院國有資產監督管理委員會(國資委)主管,中國包裝總公司主辦的國家級學術期刊;。(國內刊號: CN11-4450/T 國際刊號: ISSN1009-914X )本刊被《中國核心期刊(遴選)數據庫》《中國學術期刊(光盤版)》《萬方數據數字化期刊群》《中文科技期刊數據庫》等網絡媒體全文收錄,國內外深有影響。旨在推廣科教新理論、新經驗,為廣大教育、科研工作者提供學術交流平臺,推動科技、教育事業發展。
      【辦刊宗旨】
      本刊堅持國家科技創新、教育創新方針,傳播科技理念、報道科技動態、倡導科技創新、促進科技進步;廣泛探討交流科技、教育工作成果;多方位、多層次介紹科教創新的前沿性內容。
      【主要欄目】
      工程技術、工業技術、教學研究、建筑科學、IT技術、電子商務、資源與環境、財會審計、信息科學、學術論壇、行業科技、創新與實踐、經營管理、經濟綜述、圖書館論壇等相關欄目及內容.
      【征稿對象】
      全國教科研工作者、廣大教師、各級科技與教育管理部門領導、科研院所、科技教育人員、科教領域管理人員、各大院校的在讀博士生、碩士生及社會各界關心科教工作的人士。
      【來稿須知】
      ① 稿件應具有科學性、先進性和實用性,論點明確、論據可靠、數據準確、邏輯嚴謹、文字通順。
      ② 論文2200—6000字符數為宜,來稿請使用word排版,并請注明作者姓名、單位、通訊地址、郵編、電子信箱、聯系電話等,本刊歡迎網上投稿。本刊已被《中國核心期刊(遴選)數據
      ③ 論文的基本要素齊全,文章標題、作者單位、作者姓名、關鍵詞、摘要、結語、參考文獻等。
      ④ 所投稿件請保證文章版權的獨立性,無抄襲、署名排序無爭議、文責自負,請勿一稿多投!
      *來稿無論我刊選用與否,都會在5個工作日內將審理結果盡快通知作者,作者也可來電查詢,以免影響正常發表。
      投稿信箱: zgkjblliu@yahoo.com.cn
      【本刊誠聘特約編委、組稿編輯】
      為不斷提高刊物學術水平,我刊現面向全國教育科研系統招聘編委及組稿編輯,負責部分重點稿件的編審并幫助所在單位、系統、地區的教科研工作者解決學術論文的寫作和在本刊的發表事宜。
      權利待遇:發特約編委證書;優先在本刊刊發學術論文;本刊將會酌情付給酬金。
      詳情請來電來函咨詢:010-51347973
      E-mail:zgkjblliu@yahoo.com.cn
      【有關事宜】
      優秀稿件我們會提交參選我社論文評優活動,獲獎作品頒布發榮譽證書。
      【網上查證】
      登陸以下官方網站可對《中國科技博覽》進行網上查詢。
      北京市新聞出版局:www.bjppb.gov.cn
      中國新聞總署: www.gapp.gov.cn/
      聯 系 人:劉越(老師)
      電 話:010--51347973 13041201895
      投稿信箱: zgkjblliu@yahoo.com.cn (投稿專用)
      工 作Q Q: 793021885
      雜志社地址:北京市東城區興化路9號院大樓516室
      郵 編:100013


        回復  更多評論   

     
    Copyright © dyerac in java... Powered by: 博客園 模板提供:滬江博客
    主站蜘蛛池模板: 免费大香伊蕉在人线国产 | 亚洲sss综合天堂久久久| 亚洲一区二区三区自拍公司| 日本人护士免费xxxx视频| 亚洲精品久久无码av片俺去也| 亚洲AV中文无码乱人伦| 国产zzjjzzjj视频全免费| 国产乱子影视频上线免费观看| 国产成人精品免费视频大全五级| 成人免费无码精品国产电影| 国产91在线免费| 亚洲男人的天堂在线va拉文| 亚洲中文字幕在线乱码| 亚洲今日精彩视频| 国产区在线免费观看| 黄页网站在线视频免费| 一级全免费视频播放| 亚洲综合一区国产精品| 亚洲丶国产丶欧美一区二区三区| 怡红院亚洲红怡院在线观看| 黄色毛片免费网站| a级在线免费观看| 2019中文字幕在线电影免费 | 亚洲精品一卡2卡3卡四卡乱码| 亚洲精品无码专区在线| 一个人免费观看www视频| 国产午夜免费高清久久影院| 亚洲免费观看在线视频| 日韩在线免费电影| 亚洲免费视频一区二区三区| 婷婷精品国产亚洲AV麻豆不片 | 亚洲三级视频在线| 狠狠综合亚洲综合亚洲色| 黄色网址免费在线观看| **aaaaa毛片免费| 国产精品二区三区免费播放心| 亚洲综合色视频在线观看| 亚洲色偷偷av男人的天堂| 亚洲av成人中文无码专区| 免费在线观影网站| 免费无码精品黄AV电影|