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

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

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

    【永恒的瞬間】
    ?Give me hapy ?
    面向切面(AOP)"與"面向?qū)ο?OOP)"
    (原來(lái)是按照原作者的格式分了4篇,現(xiàn)在整理了一下.合并為一篇.?--譯者注)

    首先你要明確的一點(diǎn),AOP和OOP是兩種不同的認(rèn)識(shí)事物的角度,并不是說(shuō)有了AOP就不要用OOP.AOP所關(guān)注的是傳統(tǒng)OOP不能優(yōu)雅解決的問(wèn)題.(程序員通常都是完美主義者,當(dāng)解決某個(gè)問(wèn)題不優(yōu)雅的時(shí)候,那就意味著不完美.)下面將就一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明他們到底如何的不同.

    作為一個(gè)使用OOP多年的人來(lái)說(shuō),當(dāng)我聽(tīng)說(shuō)AOP可以解決一些OOP一直都不能優(yōu)雅地解決的問(wèn)題時(shí),我覺(jué)得應(yīng)該去探個(gè)究竟了.對(duì)兩種技術(shù)的比較最能給我們實(shí)際應(yīng)用提供見(jiàn)解.這里我設(shè)計(jì)了一個(gè)例子:一個(gè)OOP應(yīng)用,其中某些方面適合使用AOP.

    本文展示了一個(gè)簡(jiǎn)單的例子.一開(kāi)始介紹了問(wèn)題域,然后分別給出OOP與AOP的解決方案.后者使用了?JDK5.0,JUnit,和AspectWerkz.最后說(shuō)明如何編寫(xiě)代碼.讀完本文后,我希望你能知道AOP到底是什么,解決什么樣的問(wèn)題.(由于作者在后面AOP的例子中使用了Java5.0的批注(Annotation),建議讀者先有所了解.?--?譯者注).

    問(wèn)題域描述
    一個(gè)軟件公司雇傭一個(gè)程序員,指定給他一個(gè)業(yè)務(wù)部門(mén)并要求他隨時(shí)向經(jīng)理報(bào)告.當(dāng)團(tuán)隊(duì)成員完成他們的目標(biāo)時(shí),經(jīng)理會(huì)給他們相應(yīng)的獎(jiǎng)金.公司所需要的方案必須能夠增加一個(gè)新的雇員并給當(dāng)前的員工增加獎(jiǎng)金.為了方便,我們用CSV文件存儲(chǔ)數(shù)據(jù).


    圖1?解決方案模型

    類Manager(經(jīng)理)繼承自類Employee,包含一個(gè)額外的屬性,Managing?Project.一個(gè)部門(mén)可能包含很多員工.多個(gè)部門(mén)構(gòu)成了公司.暫不考慮公司這樣的一個(gè)類,因?yàn)樗趩?wèn)題域之外.

    解決方案設(shè)計(jì)
    以下流程圖描述了解決方案設(shè)計(jì).


    圖2?對(duì)象之間的交互(增加一個(gè)新的員工,指派給他一個(gè)部門(mén)和經(jīng)理)

    出于簡(jiǎn)單的考慮,本文只關(guān)注必需的細(xì)節(jié).當(dāng)然你也可以深入代碼得到你想要的其他信息.
    [link]http://www.devx.com/assets/sourcecode/13172.zip[/link]
    EmployeeServiceTestCase,一個(gè)JUnit測(cè)試用例,模擬一個(gè)最終用戶,創(chuàng)建新員工記錄,指派部門(mén)和經(jīng)理.它獲取所有可用的部門(mén)和經(jīng)理數(shù)據(jù)并顯示在圖形界面上.為了實(shí)例化域?qū)ο驜usinessUnit和Manager,獲得的記錄將傳遞給工廠類.之后,通過(guò)給EmployeeService傳遞一個(gè)引用來(lái)創(chuàng)建一個(gè)Employee對(duì)象.這個(gè)服務(wù)類使用EmployeeFactory創(chuàng)建對(duì)象,并把這個(gè)對(duì)象傳給EmployeeRepository?來(lái)進(jìn)行持久化操作.

    應(yīng)用程序中需要面向哪些"切面"
    到目前為止,對(duì)模型和設(shè)計(jì)的討論還限于一個(gè)較抽象的層面.現(xiàn)在,我轉(zhuǎn)向這個(gè)應(yīng)用的其他方面?-?這對(duì)理解AOP的價(jià)值至關(guān)重要.

    操作所需的資源
    1. public?static?Set?findAllBusinessUnits()?throws?RepositoryException?{
    2. Set?businessUnits?=?new?HashSet();
    3. try?{
    4. FileReader?businessUnitFile?=?null;
    5. BufferedReader?bufferedBusinessUnitFile?=?null;
    6. try?{
    7. businessUnitFile?=?new?FileReader(FILE_NAME);
    8. bufferedBusinessUnitFile?=?new?BufferedReader(businessUnitFile);
    9. String?businessUnitRecord;
    10. while((businessUnitRecord?=?bufferedBusinessUnitFile.readLine())?!=?null)?{
    11. BusinessUnit?businessUnit?=?BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
    12. businessUnits.add(businessUnit);
    13. }
    14. }?finally?{
    15. if(bufferedBusinessUnitFile?!=?null)?{
    16. bufferedBusinessUnitFile.close();
    17. }
    18. if(businessUnitFile?!=?null)?{
    19. businessUnitFile.close();
    20. }
    21. }
    22. }?catch(IOException?ioe)?{
    23. String?message?=?"IOError.?Unable?to?find?Business?Unit?records";
    24. logger.log(SEVERE,?message,?ioe);
    25. throw?new?RepositoryException(message,?ioe);
    26. }
    27. logger.log(INFO,?"Manager?Records?returned:"?+?businessUnits.size());
    28. return?businessUnits;
    29. }

    上面的代碼通過(guò)FileReader和BUfferedReader來(lái)讀取CSV文件中的業(yè)務(wù)數(shù)據(jù).
    應(yīng)用程序重復(fù)地從資源文件中取得數(shù)據(jù)然后在操作完成后釋放.我們會(huì)發(fā)現(xiàn):去掉程序的這兩個(gè)"切面"將提高代碼的可讀性并達(dá)到一個(gè)更好的設(shè)計(jì),因?yàn)槿サ暨@些"多余"的東西,剩下的代碼才是這個(gè)方法真正的精髓.這個(gè)方法的作用是讀取業(yè)務(wù)單位數(shù)據(jù).所以不應(yīng)該也不需要去知道"如何獲取和釋放資源以及這個(gè)過(guò)程中出現(xiàn)的異常"這個(gè)"切面".同樣地,使用AOP處理異常也變得不同.(后面將詳細(xì)介紹)

    持久層
    傳統(tǒng)的OOP使用倉(cāng)庫(kù)類(repository?classes)來(lái)打理應(yīng)用程序的持久層.即:?
    1. public?class?EmployeeRepository?{
    2. public?static?void?createEmployee(Employee?employee)?throws?RepositoryException?{
    3. //使用print?writer把數(shù)據(jù)放入csv文件
    4. }
    5. public?static?String?findEmployeeRecordById(String?id)?throws?RepositoryException?{
    6. //使用file?reader來(lái)獲得指定id的員工數(shù)據(jù)
    7. }
    8. public?static?Employee?findEmployeeById(String?id)?throws?RepositoryException?{
    9. //使用該方法獲取員工數(shù)據(jù),Employee對(duì)象由工廠類創(chuàng)建
    10. }
    11. public?static?void?updateEmployee(Employee?employee)?{
    12. //更新員工數(shù)據(jù)
    13. }
    14. }

    類EmployeeService?使用一個(gè)倉(cāng)庫(kù)類給應(yīng)用中相關(guān)雇員提供服務(wù),在一個(gè)企業(yè)應(yīng)用中,從域模型(domain?model)中去掉持久層代碼是一種設(shè)計(jì)上的改進(jìn).模型設(shè)計(jì)者和程序員就可以關(guān)注各自的業(yè)務(wù)邏輯和持久層處理.后面你將會(huì)看到如何通過(guò)AOP來(lái)達(dá)到這樣的效果.

    日志
    刪除用于調(diào)試的日志代碼將會(huì)極大地改進(jìn)代碼的可讀性.考慮下面的代碼片斷:
    1. public?Employee?createEmployee(String?name,
    2. String?contactNumber,
    3. BusinessUnit?businessUnit,
    4. Manager?manager)
    5. throws?EmployeeServiceException?{
    6. String?id?=?createNewEmployeeId();
    7. Employee?employee?=
    8. EmployeeFactory.createEmployee(id,?name,?contactNumber,?businessUnit,?manager);
    9. try?{
    10. EmployeeRepository.createEmployee(employee);
    11. }?catch(RepositoryException?re)?{
    12. String?message?=?"Created?employee?successfully:"?+?employee;
    13. logger.log(SEVERE,?message);
    14. throw?new?EmployeeServiceException(message,?re);
    15. }
    16. logger.log(INFO,?"Created?employee?successfully:"?+?employee);
    17. return?employee;
    18. }

    上面的代碼里包含了一個(gè)致命錯(cuò)誤和一個(gè)成功信息.輸出日志這一"切面"同樣可以移到業(yè)務(wù)模型外獨(dú)立實(shí)現(xiàn).

    異常處理
    異常處理的例子我這里不再贅述,但這節(jié)已經(jīng)通過(guò)上面的代碼討論了潛在的問(wèn)題.當(dāng)你調(diào)用EmployeeRepository?對(duì)象的createEmployee?方法時(shí),你可能會(huì)得到一個(gè)RepositoryException異常.傳統(tǒng)的解決方法是,在這個(gè)類中處理.另一種方法是,當(dāng)RepositoryException?異常被拋出時(shí)createEmployee?方法返回null,catch塊中的其他邏輯可以在類外處理這一錯(cuò)誤.
    錯(cuò)誤處理在不同的情況中也會(huì)不同.但是,通過(guò)AOP可以區(qū)分開(kāi)每種情況.


    圖3

    圖3中描述了AOP方法的設(shè)計(jì)以及在一個(gè)更抽象的層次上類間的交互.你可以通過(guò)對(duì)比圖1和圖3來(lái)更好地理解AOP.
    程序的目的是通過(guò)BusinessUnit對(duì)象讀取CSV文件中的記錄然后?填入類BusinessUnitService?中的map.使用AOP來(lái)填充這個(gè)map有點(diǎn)類似后門(mén)(backdoor)方法?--?控制被委派給BusinessUnit?來(lái)讀取存儲(chǔ)介質(zhì)中的記錄.

    AOP就是定義一些切入點(diǎn)(pointcut)和處理方法(advice).一個(gè)"切入點(diǎn)"是源代碼中一個(gè)執(zhí)行點(diǎn).前面的例子定義了一個(gè)"切入點(diǎn)"?--??類BusinessUnitService中的findBusinessUnits方法.一個(gè)"處理方法"顧名思義就是當(dāng)執(zhí)行到某個(gè)"切入點(diǎn)"時(shí)的一塊代碼.類BusinessUnitPersistentAspect?包括advice方法findAllBusinessUnits,該方法從存儲(chǔ)介質(zhì)中載入數(shù)據(jù),然后使用工廠類創(chuàng)建BusinessUnit?對(duì)象.然后這個(gè)對(duì)象被加入map,map對(duì)象的引用通過(guò)BusinessUnitService?對(duì)象獲得."切入點(diǎn)"和"處理方法"組成了所謂的"切面(Aspect)"

    為了讀取存儲(chǔ)介質(zhì)中的數(shù)據(jù),OOP方法通過(guò)一個(gè)DAO類來(lái)做.而AOP中,你只要定義一個(gè)"切入點(diǎn)"和相應(yīng)的"處理方法"來(lái)讀取數(shù)據(jù).AOP框架會(huì)以advice的形式注入代碼,既可以在執(zhí)行期也可以在編譯期.

    總而言之,當(dāng)類BusinessUnitService?中的findAllBusinessUnits?方法被調(diào)用時(shí),AOP框架會(huì)在"切入點(diǎn)"處注入處理方法,通過(guò)BusinessUnit?對(duì)象預(yù)先讀取數(shù)據(jù)來(lái)填充map對(duì)象.這樣,持久層方面的代碼就可以移到業(yè)務(wù)代碼之外了.

    新方法里的"切面"

    本節(jié)討論如何用AOP為應(yīng)用程序的各個(gè)"切面"建模

    操作資源

    類BusinessUnitPersistenceAspect?的持久方法使用了一個(gè)buffered?reader.你甚至可以定義"切面"的"切面",但為了簡(jiǎn)單,這里只關(guān)注類的查找方法.
    1. @Aspect("perJVM")
    2. public?class?BufferedFileReaderAspect?{
    3. @Expression("execution(*?org.javatechnocrats.aop.withaop.aspects.BusinessUnitPersistenceAspect.find*(..))")
    4. Pointcut?businessUnitPersistenceAspect;
    5. //?其他"切入點(diǎn)"定義
    6. @Expression("businessUnitPersistenceAspect?||
    7. employeePersistenceAspect?||
    8. managerPersistenceAspect")
    9. Pointcut?allPersistencePointcuts;
    10. private?Map<Class,?String>?fileNames;
    11. public?BufferedFileReaderAspect()?{
    12. System.out.println("BufferedFileReaderAspect?created");
    13. fileNames?=?new?HashMap<Class,?String>();
    14. fillFileNames();
    15. }
    16. @Before("allPersistencePointcuts")
    17. public?void?assignReader(JoinPoint?joinPoint)?throws?Throwable?{
    18. System.out.println("assignReader?advice?called");
    19. Object?callee?=?joinPoint.getCallee();
    20. IBufferedFileReaderConsumable?bufReaderConsumable?=?(IBufferedFileReaderConsumable)callee;
    21. Class?persistenceClass?=?callee.getClass();
    22. String?fileName?=?fileNames.get(persistenceClass);
    23. FileReader?fileReader?=?new?FileReader(fileName);
    24. BufferedReader?bufferedReader?=?new?BufferedReader(fileReader);
    25. bufReaderConsumable.setBufferedReader(bufferedReader);
    26. }
    27. @AfterFinally("allPersistencePointcuts")
    28. public?void?releaseReader(JoinPoint?joinPoint)?throws?Throwable?{
    29. //釋放buffered?reader等資源
    30. }
    31. //其他方法
    32. }


    上面的代碼試圖為每一個(gè)方法創(chuàng)建"切入點(diǎn)"--?所有以find開(kāi)頭的方法.無(wú)論何時(shí)這些方法被調(diào)用,assignReader方法都會(huì)被提前執(zhí)行.這里它獲取被調(diào)用的類實(shí)例然后設(shè)置新建的buffered?reader.

    同樣地,在releaseReader?方法里,代碼會(huì)預(yù)先關(guān)閉buffered?reader集合.本節(jié)只解釋@before和@
    AfterFinally?這兩個(gè)"切入點(diǎn)".(以J2SE?5.0的標(biāo)記定義).另外,你也可以在方面定義的xml文件中聲明他們.你可以查看例程源代碼中的aop.xml文件.

    下載

    持久化

    前面提到,OOP方法使用BusinessUnit?來(lái)為應(yīng)用的持久層填充Map.在下面的高亮代碼中(@before一行,以及while循環(huán)代碼?-?譯者注),當(dāng)BusinessUnitService?中的方法findAllBusinessUnits?被調(diào)用時(shí)"處理方法"findAllBusinessUnits?也將被調(diào)用.
    1. @Aspect("perJVM")
    2. public?class?BusinessUnitPersistenceAspect?implements?IBufferedFileReaderConsumable?{
    3. private?BufferedReader?buffFileReader;
    4. @Before("execution(Collection?org.javatechnocrats.aop.withaop.BusinessUnitService.findAllBusinessUnits())")
    5. public?void?findAllBusinessUnits(JoinPoint?joinPoint)?throws?Throwable?{
    6. System.out.println("findAllBusinessUnits?advice?called");
    7. Map<String,?BusinessUnit>?businessUnits?=
    8. ((BusinessUnitService)joinPoint.getThis()).getBusinessUnits();
    9. String?businessUnitRecord;
    10. while((businessUnitRecord?=?buffFileReader.readLine())?!=?null)?{
    11. BusinessUnit?businessUnit?=?BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
    12. businessUnits.put(businessUnit.getId(),?businessUnit);
    13. }
    14. }
    15. public?void?setBufferedReader(BufferedReader?buffFileReader)?{
    16. System.out.println("BusinessUnitPersistenceAspect.setBufferedReader?called");
    17. this.buffFileReader?=?buffFileReader;
    18. }
    19. public?BufferedReader?getBufferedReader()?{
    20. System.out.println("BusinessUnitPersistenceAspect.getBufferedReader?called");
    21. return?this.buffFileReader;
    22. }
    23. }

    "處理方法"從數(shù)據(jù)存儲(chǔ)中讀取記錄,使用工廠類創(chuàng)建一個(gè)BusinessUnit實(shí)例.然后這個(gè)實(shí)例被加入到Map.該Map掌管程序的所有持久化"切面".

    日志
    本文中的例子沒(méi)有包含一個(gè)完整的日志AOP解決方案.但是,它為java.lang.Object類的toString方法定義了一個(gè)"切入點(diǎn)"來(lái)獲取類的調(diào)試信息.因此,域中的類不需要實(shí)現(xiàn)toString方法.通常可能你可能需要為每一個(gè)類都要實(shí)現(xiàn)這個(gè)方法.

    1. @Aspect("perJVM")
    2. public?class?LoggingAspect?{
    3. @Around("execution(String?org.javatechnocrats.aop.withaop..*.toString())")
    4. public?Object?toStringAdvice(JoinPoint?joinPoint)?throws?Throwable?{
    5. System.out.println("toStringAdvice?called");
    6. String?toString?=?(String)joinPoint.proceed();
    7. Object?target?=?joinPoint.getThis();
    8. Field?fields[]?=?target.getClass().getDeclaredFields();
    9. List?members?=?new?ArrayList(fields.length?+?1);
    10. members.add(toString);
    11. for(Field?field?:?fields)?{
    12. field.setAccessible(true);
    13. Object?member?=?field.get(target);
    14. members.add(field.getName()?+?"="?+?member);
    15. }
    16. return?members.toString();
    17. }

    你也可以用這個(gè)樣例代碼完成錯(cuò)誤處理"切面".

    深入源代碼

    為了理解樣例需求的OOP設(shè)計(jì),請(qǐng)參看源代碼并思考以下幾個(gè)問(wèn)題:?下載

    *?首先分析oldway包中EmployeeServiceTestCase?類中的代碼
    *查看testEmployeeCredit?方法
    *搞懂業(yè)務(wù)類Employee和BusinessUnit
    *學(xué)習(xí)?service,repository和factory概念.這些是業(yè)務(wù)驅(qū)動(dòng)設(shè)計(jì)的主要概念.
    *更深入地理解oldway包中的service,repository和factory類

    而AOP地理解則應(yīng)該是:
    *分析newway包中EmployeeServiceTestCase?類
    *查看service,repository和factory類,基本和前一種差不多.只是你要讓"處理方法"截獲程序的執(zhí)行流程.
    *研究aspect類學(xué)習(xí)"切入點(diǎn)"的定義

    要執(zhí)行程序,你需要做的工作:
    *?下載AspectWerkz?2.0?http://aspectwerkz.codehaus.org/
    *設(shè)置以下的環(huán)境變量:
    set?JAVA_HOME=c:\Program?Files\Java\jdk1.5.0
    set?ASPECTWERKZ_HOME=C:\aw_2_0_2
    set?PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
    set?CLASSPATH=
    C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar;?classes;C:\?junit\3.8.1\resources\lib\junit.jar
    *解壓縮源代碼和其他文件
    *編譯Java文件,但不要編譯測(cè)試用例否則你調(diào)試時(shí)會(huì)遇到一個(gè)錯(cuò)誤.
    *進(jìn)行離線調(diào)試.假設(shè)你把文件解壓縮到c:\aop?,類文件解壓到c:\aop\classes,在c:\aop目錄下執(zhí)行以下命令:
    %ASPECTWERKZ_HOME%\bin\aspectwerkz?-offline?etc/aop.xml?-cp?classes?classes
    *AOP框架會(huì)修改類來(lái)注入必要的字節(jié)碼
    *編譯測(cè)試用例,使用JUnit運(yùn)行它.

    后記
    當(dāng)你完成了上面的這些工作,你應(yīng)該有以下的領(lǐng)悟:
    *程序中的交叉關(guān)聯(lián)
    *關(guān)于AOP中深入源代碼

    為了理解樣例需求的OOP設(shè)計(jì),請(qǐng)參看源代碼并思考以下幾個(gè)問(wèn)題:?下載

    *?首先分析oldway包中EmployeeServiceTestCase?類中的代碼
    *查看testEmployeeCredit?方法
    *搞懂業(yè)務(wù)類Employee和BusinessUnit
    *學(xué)習(xí)?service,repository和factory概念.這些是業(yè)務(wù)驅(qū)動(dòng)設(shè)計(jì)的主要概念.
    *更深入地理解oldway包中的service,repository和factory類

    而AOP地理解則應(yīng)該是:
    *分析newway包中EmployeeServiceTestCase?類
    *查看service,repository和factory類,基本和前一種差不多.只是你要讓advice截取程序的流程.
    *研究aspect類學(xué)習(xí)point?cut的定義

    要執(zhí)行程序,你需要做的工作:
    *?下載AspectWerkz?2.0?http://aspectwerkz.codehaus.org/
    *設(shè)置以下的環(huán)境變量:
    set?JAVA_HOME=c:\Program?Files\Java\jdk1.5.0
    set?ASPECTWERKZ_HOME=C:\aw_2_0_2
    set?PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
    set?CLASSPATH=
    C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar;?classes;C:\?junit\3.8.1\resources\lib\junit.jar
    *解壓縮源代碼和其他文件
    *編譯Java文件,但不要編譯測(cè)試用例否則你調(diào)試時(shí)會(huì)遇到一個(gè)錯(cuò)誤.
    *進(jìn)行離線調(diào)試.假設(shè)你把文件解壓縮到c:\aop?,類文件解壓到c:\aop\classes,在c:\aop目錄下執(zhí)行以下命令:
    %ASPECTWERKZ_HOME%\bin\aspectwerkz?-offline?etc/aop.xml?-cp?classes?classes
    *AOP框架會(huì)修改類來(lái)注入必要的字節(jié)碼
    *編譯測(cè)試用例,使用JUnit運(yùn)行它.

    后記
    當(dāng)你完成了上面的這些工作,你應(yīng)該有以下的領(lǐng)悟:
    *程序中的交叉關(guān)聯(lián)
    *關(guān)于AOP中"切面"的含義
    *如何用AOP來(lái)把程序業(yè)務(wù)層中的交叉關(guān)聯(lián)分離出來(lái),使用"切入點(diǎn)"和"處理方法"
    *OOP和AOP時(shí)在程序控制流上的不同

    從本文你應(yīng)該也得到一種看待實(shí)際開(kāi)發(fā)的新視角.你應(yīng)該有信心使用AOP來(lái)改進(jìn)項(xiàng)目中的設(shè)計(jì),建模,提高代碼的重用性.至少,你可以開(kāi)始使用AOP來(lái)處理日志,錯(cuò)誤和持久化.

    個(gè)人覺(jué)得,AOP的學(xué)習(xí)曲線相對(duì)較陡,尤其在理解定義"切入點(diǎn)"的句法時(shí).理想的情況是,使用OOP來(lái)設(shè)計(jì)業(yè)務(wù)模型,使用AOP把業(yè)務(wù)模型中的交叉關(guān)聯(lián)移出,從而使代碼簡(jiǎn)潔并提高可讀性.

    AOP的一個(gè)缺點(diǎn)是會(huì)使調(diào)試變得困難,因?yàn)椴煌贠OP,程序流變的復(fù)雜了,交互是由編譯期或執(zhí)行期決定.我準(zhǔn)備將來(lái)做一些自動(dòng)化工具來(lái)解決這個(gè)問(wèn)題.

    posted on 2007-02-02 19:51 ???MengChuChen 閱讀(185) 評(píng)論(0)  編輯  收藏

    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 最近中文字幕大全免费版在线| 天天看免费高清影视| 亚洲精品国产高清在线观看| 激情97综合亚洲色婷婷五 | 国产精品国产自线拍免费软件| 最近中文字幕2019高清免费| 美女网站在线观看视频免费的| 亚洲精品av无码喷奶水糖心| 亚洲VA中文字幕无码一二三区| 四虎永久在线精品视频免费观看| 国产精品爱啪在线线免费观看| 久久亚洲免费视频| 国产男女爽爽爽免费视频| 黄色一级免费网站| 亚洲人成未满十八禁网站| 亚洲色图在线播放| 亚洲午夜久久久影院| 亚洲国产午夜福利在线播放| 国产又大又粗又硬又长免费| 免费鲁丝片一级在线观看| 最近免费中文字幕大全免费 | 亚洲AV无码乱码精品国产| 美女被免费视频网站a国产| 日韩免费一区二区三区在线播放| 久久国产乱子精品免费女| 视频免费1区二区三区| 免费精品国自产拍在线播放| 亚洲Av无码国产一区二区| 亚洲AV综合永久无码精品天堂| 亚洲成人免费在线观看| 亚洲一区中文字幕久久| 亚洲av无码片在线播放| 亚洲av无码一区二区三区不卡| 亚洲国产精品无码AAA片| 九月丁香婷婷亚洲综合色| 国产亚洲精品资源在线26u| 亚洲精品美女久久久久99| 亚洲综合无码精品一区二区三区 | 亚洲 日韩经典 中文字幕| 一本色道久久综合亚洲精品蜜桃冫| 亚洲图片校园春色|