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

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

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

    樂在其中

    以JEE為主攻,以Flex為點(diǎn)綴,以Eclipse RCP為樂趣
    請(qǐng)?jiān)L問http://www.inframesh.org

    首頁 新隨筆 聯(lián)系 管理
      43 Posts :: 0 Stories :: 8 Comments :: 0 Trackbacks

    一個(gè)用于J2EE應(yīng)用程序的異常處理框架


      在大多數(shù)Java項(xiàng)目中,大部分代碼都是樣板代碼。異常處理就屬于此類代碼。即使業(yè)務(wù)邏輯只有3到4行代碼,用于異常處理的代碼也要占10到20行。本文將討論如何讓異常處理保持簡(jiǎn)單和直觀,使開發(fā)人員可以專心于開發(fā)業(yè)務(wù)邏輯,而不是把時(shí)間浪費(fèi)在編寫異常處理的樣板代碼上。本文還將說明用于在J2EE環(huán)境中創(chuàng)建和處理異常的基礎(chǔ)知識(shí)和指導(dǎo)原則,并提出了一些可以使用異常解決的業(yè)務(wù)問題。本文將使用Struts框架作為表示實(shí)現(xiàn),但該方法適用于任何表示實(shí)現(xiàn)。

      使用checked和unchecked異常的場(chǎng)景

      您是否曾經(jīng)想過,為什么要在編寫好的代碼塊周圍放置一個(gè)try-catch塊,即便明知道無法對(duì)這些異常進(jìn)行什么處理,而只滿足于把它們放在catch塊中?您可能想知道,為什么不能把這項(xiàng)工作放在一個(gè)集中的地方完成?在大多數(shù)情況下,這個(gè)地方對(duì)于J2EE應(yīng)用程序來說就是一個(gè)前端控制器。換句話說,開發(fā)人員不會(huì)因?yàn)樗鼈兌艿礁蓴_,因?yàn)楦静槐睾芏嗟剡^問它們。但是,如果一個(gè)方法名稱包含一個(gè)throws子句,會(huì)出現(xiàn)什么情況呢?開發(fā)人員或者必須捕捉這些異常,或者把它們放在自己的方法的throws子句中。這就是痛苦的根源!幸運(yùn)的是,Java API有一類叫做unchecked exception的異常,它們不必捕捉。但是,仍然存在一個(gè)問題:根據(jù)什么來決定哪些是checked異常,哪些是unchecked異常?下面給出一些指導(dǎo)原則:

      終端用戶無法采取有效操作的異常應(yīng)該作為unchecked異常。例如,致命的和不可恢復(fù)的異常就應(yīng)該是unchecked。把XMLParseException(在解析XML文件時(shí)拋出)作為checked異常沒有任何意義,因?yàn)槲┮荒軌虿扇〉拇胧┚褪腔诋惓8檨斫鉀Q根本問題。通過擴(kuò)展java.lang.RuntimeException,可以創(chuàng)建自定義的unchecked異常。

      在應(yīng)用程序中,與用戶操作相關(guān)的異常應(yīng)該是checked異常。checked異常要求客戶端來捕捉它們。您可能會(huì)問,為什么不把所有異常都當(dāng)作是unchecked。這樣做的問題在于,其中一些異常無法在正確的位置被捕捉到。這會(huì)帶來更大的問題,因?yàn)殄e(cuò)誤只有在運(yùn)行時(shí)才能被識(shí)別。checked異常的例子有業(yè)務(wù)確認(rèn)異常、安全性異常等等。

      異常拋出策略

      只捕捉基本應(yīng)用程序異常(假定為BaseAppException)并在throws子句中聲明

      在大多數(shù)J2EE應(yīng)用程序中,關(guān)于針對(duì)某個(gè)異常應(yīng)該在哪一界面上顯示哪條錯(cuò)誤消息的決策只能在表示層中做出。這會(huì)帶來另一個(gè)問題:為什么我們不能把這種決策放在一個(gè)公共的地方呢?在J2EE應(yīng)用程序中,前端控制器就是一個(gè)進(jìn)行常見處理的集中位置。

      此外,必須有一種用于傳播異常的通用機(jī)制。異常也需要以一種普適的方式得到處理。為此,我們始終需要在控制器端捕捉基本應(yīng)用程序異常BaseAppException。這意味著我們需要把BaseAppException異常(只有這個(gè)異常)放入可以拋出checked異常的每個(gè)方法的throws子句中。這里的概念是使用多態(tài)來隱藏異常的實(shí)際實(shí)現(xiàn)。我們?cè)诳刂破髦胁蹲紹aseAppException,但是所拋出的特定異常實(shí)例可能是幾個(gè)派生異常類中的任意一個(gè)。借助于這種方法,可以獲得許多異常處理方面的靈活性:

      不需要在throws子句中放入大量的checked異常。throws子句中只需要有一個(gè)異常。

      不需要再對(duì)應(yīng)用程序異常使用混亂的catch塊。如果需要處理它們,一個(gè)catch塊(用于BaseAppException)就足夠了。

      開發(fā)人員不需要親自進(jìn)行異常處理(日志記錄以及獲取錯(cuò)誤代碼)。這種抽象是由ExceptionHandler完成的,稍后本文會(huì)就此點(diǎn)進(jìn)行討論。

      即使稍后把更多異常引入到方法實(shí)現(xiàn)中,方法名稱也不會(huì)改變,因此也不需要修改客戶端代碼,否則就會(huì)引起連鎖反應(yīng)。然而,拋出的異常需要在方法的Javadoc中指定,以便讓客戶端可以看到方法約束。

      下面給出拋出checked異常的一個(gè)例子:

    public void updateUser(UserDTO userDTO)
    throws BaseAppException{
     UserDAO userDAO = new UserDAO();
     UserDAO.updateUser(userDTO);
     ...
     if(...)
     throw new RegionNotActiveException("Selected region is not active");
    }

    Controller Method:
    ...
    try{
     User user = new User();
     user.updateUser(userDTO);
    }catch(BaseAppException ex){
     //ExceptionHandler is used to handle
     //all exceptions derived from BaseAppException
    }
    ...

      迄今為止,我們已經(jīng)說明,對(duì)于所有可能拋出checked異常并被Controller調(diào)用的方法,其throws子句中應(yīng)該只包含checked異常。然而,這實(shí)際上暗示著我們?cè)趖hrows子句中不能包含其他任何應(yīng)用程序異常。但是,如果需要基于catch塊中某種類型的異常來執(zhí)行業(yè)務(wù)邏輯,那又該怎么辦呢?要處理這類情況,方法還可以拋出一個(gè)特定異常。記住,這是一種特例,開發(fā)人員絕對(duì)不能認(rèn)為這是理所當(dāng)然的。同樣,此處討論的應(yīng)用程序異常應(yīng)該擴(kuò)展BaseAppException類。下面給出一個(gè)例子:

    CustomerDAO method:
    //throws CustomerNotActiveException along with
    //BaseAppException
    public CustomerDTO getCustomer(InputDTO inputDTO)
    throws BaseAppException,
    CustomerNotActiveException {
     . . .
     //Make a DB call to fetch the customer
     //details based on inputDTO
     . . .
     // if not details found
     throw new CustomerNotActiveException("Customer is not active");
    }

    Client method:

    //catch CustomerNotActiveException
    //and continues its processing
    public CustomerDTO getCustomerDetails(UserDTO userDTO)
    throws BaseAppException{
     ...
     CustomerDTO custDTO = null;
     try{
      //Get customer details
      //from local database
      customerDAO.getCustomerFromLocalDB();
     }catch(CustomerNotActiveException){
      ...
      return customerDAO .activateCustomerDetails();
     }
    }

      在web應(yīng)用程序?qū)哟紊咸幚韚nchecked異常

      所有unchecked異常都應(yīng)該在web應(yīng)用程序?qū)哟紊线M(jìn)行處理。可以在web.xml文件中配置web頁面,以便當(dāng)應(yīng)用程序中出現(xiàn)unchecked異常時(shí),可以把這個(gè)web頁面顯示給終端用戶。

      把第三方異常包裝到特定于應(yīng)用程序的異常中

      當(dāng)一個(gè)異常起源于另一個(gè)外部接口(組件)時(shí),應(yīng)該把它包裝到一個(gè)特定于應(yīng)用程序的異常中,并進(jìn)行相應(yīng)處理。

      例子:

    try {
     BeanUtils.copyProperties(areaDTO, areaForm);
    } catch (Exception se) {
     throw new CopyPropertiesException("Exception occurred while using
    copy properties", se);
    }

      這里,CopyPropertiesException擴(kuò)展了java.lang.RuntimeException,我們將會(huì)記錄它。我們捕捉的是Exception,而不是copyProperties方法可以拋出的特定checked異常,因?yàn)閷?duì)于所有這些異常來說,我們都會(huì)拋出同一個(gè)unchecked CopyPropertiesException異常。

      過多異常

      您可能想知道,如果我們?yōu)槊織l錯(cuò)誤消息創(chuàng)建一個(gè)異常,異常類自身是否會(huì)溢出呢?例如,如果“Order not found”是OrderNotFoundException的一條錯(cuò)誤消息,您肯定不會(huì)讓CustomerNotFoundException的錯(cuò)誤消息為“Customer not found”,理由很明顯:這兩個(gè)異常代表同樣的意義,惟一的區(qū)別在于使用它們的上下文不同。所以,如果可以在處理異常時(shí)指定上下文,我們無疑可以把這些異常合并為一個(gè)RecordNotFoundException。下面給出一個(gè)例子:

    try{
     ...
    }catch(BaseAppException ex){
     IExceptionHandler eh =ExceptionHandlerFactory.getInstance().create();
      ExceptionDTO exDto = eh.handleException("employee.addEmployee", userId, ex);
    }

      在這里,employee.addEmployee上下文將被附加給一個(gè)上下文敏感的異常的錯(cuò)誤代碼,從而產(chǎn)生惟一的錯(cuò)誤代碼。例如,如果RecordNotFoundException的錯(cuò)誤代碼是errorcode.recordnotfound,那么這個(gè)上下文的最終錯(cuò)誤代碼將變?yōu)閑rrorcode.recordnotfound.employee.addEmployee,它對(duì)于這個(gè)上下文是惟一的錯(cuò)誤代碼。

      然而,我們要給出一個(gè)警告:如果您準(zhǔn)備在同一個(gè)客戶端方法中使用多個(gè)接口,而且這些接口都可以拋出RecordNotFoundException異常,那么想要知道是哪個(gè)實(shí)體引發(fā)了這個(gè)異常就變得十分困難。如果業(yè)務(wù)接口是公共的,而且可以被各種外部客戶端使用,那么建議只使用特定的異常,而不使用像RecordNotFoundException這樣的一般性異常。特定于上下文的異常對(duì)于基于數(shù)據(jù)庫(kù)的可恢復(fù)異常來說非常有用,因?yàn)樵谶@種情況下,異常類始終是相同的,不同的只有它們出現(xiàn)的上下文。

      J2EE應(yīng)用程序的異常層次結(jié)構(gòu)

      正如前面討論的那樣,我們需要定義一個(gè)異常基類,叫做BaseAppException,它包含了所有應(yīng)用程序異常的默認(rèn)行為。我們將把這個(gè)基類放到所有可能拋出checked異常的方法的throws子句中。應(yīng)用程序的所有checked異常都應(yīng)該是這個(gè)基類的子類。有多種定義錯(cuò)誤處理抽象的方式。然而,其中的區(qū)別更多地是與業(yè)務(wù)類而不是與技術(shù)有關(guān)。對(duì)錯(cuò)誤處理的抽象可分為以下幾類。所有這些異常類都是從BaseAppException派生而來。

      checked異常

      業(yè)務(wù)異常:執(zhí)行業(yè)務(wù)邏輯時(shí)出現(xiàn)的異常。BaseBusinessException是這類異常的基類。

      數(shù)據(jù)庫(kù)異常:與持久化機(jī)制進(jìn)行交互時(shí)拋出的異常。BaseDBException是這類異常的基類。

      安全性異常:執(zhí)行安全性操作時(shí)出現(xiàn)的異常。這類異常的基類是BaseSecurityException。

      確認(rèn)異常:在從終端用戶處獲得確認(rèn)以執(zhí)行某個(gè)特定任務(wù)時(shí)使用。這類異常的基類是BaseConfirmationException。

      unchecked異常

      系統(tǒng)異常:有時(shí)候我們希望使用unchecked異常。例如下面的情況:不想親自處理來自第三方庫(kù)API的異常,而是希望把它們包裝在unchecked異常中,然后拋出給控制器。有時(shí)會(huì)出現(xiàn)配置問題,這些問題也不能由客戶端進(jìn)行處理,而應(yīng)該被當(dāng)作unchecked異常。所有自定義的unchecked異常都應(yīng)該擴(kuò)展自java.lang.RuntimeException類。

      表示層上的異常處理

      表示層獨(dú)自負(fù)責(zé)決定對(duì)一個(gè)異常采取什么操作。這種決策涉及到識(shí)別拋出異常的錯(cuò)誤代碼。此外,我們還需要知道在處理錯(cuò)誤之后應(yīng)該把錯(cuò)誤消息重定向到哪一界面。

      我們需要對(duì)基于異常類型獲得錯(cuò)誤代碼這個(gè)過程進(jìn)行抽象。必要時(shí)還應(yīng)該執(zhí)行日志記錄。我們把這種抽象稱之為ExceptionHandler。它基于“四人幫”(Gang of Four,GOF) 外觀模式(《Design Patterns》一書中說,該模式是用于“為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一接口。外觀定義了一個(gè)更高級(jí)別的接口,使子系統(tǒng)變得更加易于使用。”),是用于處理所有派生自BaseAppException的異常的整個(gè)異常處理系統(tǒng)的外觀。下面給出一個(gè)在Struts Action方法中進(jìn)行異常處理的例子:

    try{
     ...
     DivisionDTO storeDTO = divisionBusinessDelegate.getDivisionByNum(fromDivisionNum);
    }catch(BaseAppException ex){
     IExceptionHandler eh = ExceptionHandlerFactory.getInstance().create();
     String expContext = "divisionAction.searchDivision";
     ExceptionDTO exDto = eh.handleException(expContext , userId, ex);
     ActionErrors errors = new ActionErrors();
     errors.add(ActionErrors.GLOBAL_ERROR,new ActionError(
    exDto.getMessageCode()));
     saveErrors(request, errors);
     return actionMapping.findForward("SearchAdjustmentPage");
    }

      如果更仔細(xì)地觀察我們剛剛編寫的異常處理代碼,您可能會(huì)意識(shí)到,為每個(gè)Struts方法編寫的代碼是十分相似的,這也是一個(gè)問題。我們的目標(biāo)是盡可能地去掉樣板代碼。我們需要再次對(duì)它進(jìn)行抽象。

      解決方案是使用模板方法(Template Method)設(shè)計(jì)模式(引自GOF:“它用于實(shí)現(xiàn)一個(gè)算法的不變部分,并把可變的算法部分留給子類來實(shí)現(xiàn)。”)。我們需要一個(gè)包含模板方法形式算法的基類。該算法將包含用于BaseAppException的try-catch塊和對(duì)dispatchMethod方法的調(diào)用,方法實(shí)現(xiàn)(委托給派生類)如下面的基于Struts的Action中所示:

    public abstract class BaseAppDispatchAction
    extends DispatchAction{
     ...
     protected static ThreadLocal
     expDisplayDetails = new ThreadLocal();

     public ActionForward execute(ActionMapping mapping,ActionForm form,
    HttpServletRequest request,HttpServletResponse response) throws Exception{
      ...
      try{
       String actionMethod = request.getParameter(mapping.getParameter());
       finalDestination =dispatchMethod(mapping,form, request, response,actionMethod);
      }catch (BaseAppException Ex) {
       ExceptionDisplayDTO expDTO = (ExceptionDisplayDTO)expDisplayDetails
    .get();
       IExceptionHandler expHandler = ExceptionHandlerFactory
    .getInstance().create();
       ExceptionDTO exDto = expHandler.handleException(
    expDTO.getContext(), userId, Ex);
       ActionErrors errors = new ActionErrors();
       errors.add(ActionErrors.GLOBAL_ERROR, new ActionError(exDto .getMessageCode()));
       saveErrors(request, errors);
       return mapping.findForward(expDTO.getActionForwardName());
      } catch(Throwable ex){
       //log the throwable
       //throw ex;
      } finally {
       expDisplayDetails.set(null);
      }

      在Struts中,DispatchAction::dispatchMethod方法用于把請(qǐng)求轉(zhuǎn)發(fā)給正確的Action方法,叫做actionMethod。

      我們假定從一個(gè)HTTP請(qǐng)求獲得searchDivision作為actionMethod:dispatchMethod將在BaseAppDispatchAction的派生Action類中把請(qǐng)求分派給searchDivision方法。在這里,您可以看到,異常處理僅在基類中完成,而派生類則只實(shí)現(xiàn)Action方法。這采用了模板方法設(shè)計(jì)模式,在該模式中,異常處理部分是保持不變的,而dispatchMethod方法的實(shí)際實(shí)現(xiàn)(可變部分)則交由派生類完成。

      修改后的Struts Action方法如下所示:

    ...
    String exceptionActionForward = "SearchAdjustmentPage";
    String exceptionContext = "divisionAction.searchDivision";

    ExceptionDisplayDTO expDTO = new ExceptionDisplayDTO(expActionForward,
    exceptionContext);
    expDisplayDetails.set(expDTO);
    ...
    DivisionDTO divisionDTO =divisionBusinessDelegate.getDivisionByNum(fromDivisionNum);
    ...

      現(xiàn)在它看起來相當(dāng)清晰。因?yàn)楫惓L幚硎窃谝粋€(gè)集中的位置上(BaseAppDispatchAction)完成的,手動(dòng)錯(cuò)誤可能造成的影響也降至最低。

      然而,我們需要設(shè)置異常上下文和ActionForward方法的名稱,如果有異常出現(xiàn),請(qǐng)求就會(huì)被轉(zhuǎn)發(fā)到該方法。我們將在ThreadLocal變量expDisplayDetails中設(shè)置這些內(nèi)容。

      但是,為什么要使用java.lang.ThreadLocal變量呢?expDisplayDetails是BaseAppDispatchActiion類中的一個(gè)受保護(hù)數(shù)據(jù)成員,這也是它需要是線程安全的原因。java.lang.ThreadLocal對(duì)象在這里便可派上用場(chǎng)。

      異常處理程序

      在上一部分中,我們討論了如何對(duì)異常處理進(jìn)行抽象。下面給出一些應(yīng)該滿足的約束:

      識(shí)別異常類型并獲得相應(yīng)的錯(cuò)誤代碼,該錯(cuò)誤代碼可用于顯示一條消息給終端用戶。

      記錄異常。底層的日志記錄機(jī)制被隱藏,可以基于一些環(huán)境屬性對(duì)其進(jìn)行配置。

      您可能已經(jīng)注意到了,我們?cè)诒硎緦又胁蹲降奈┮划惓>褪荁aseAppException。由于所有checked異常都是BaseAppException的子類,這意味著我們要捕捉BaseAppException的所有派生類。基于類名稱來識(shí)別錯(cuò)誤代碼再容易不過了。

    //exp is an object of BaseAppException
    String className = exp.getClass().getName();

      可以基于異常類的名稱在一個(gè)XML文件(exceptioninfo.xml)中對(duì)錯(cuò)誤代碼進(jìn)行配置。下面給出異常配置的一個(gè)例子:

    <exception name="EmployeeConfirmationException">
     <messagecode>messagecode.laborconfirmation</messagecode>
     <confirmationind>true</confirmationind>
     <loggingtype>nologging</loggingtype>
    </exception>

      正如您看到的那樣,我們把這個(gè)異常變?yōu)轱@式,要使用的消息代碼是messagecode.employeeconfirmation。然后,為了實(shí)現(xiàn)國(guó)際化的目的,可以從ResourceBundle提取實(shí)際的消息。我們很清楚,不需要對(duì)這類異常執(zhí)行日志記錄,因?yàn)樗皇且粭l確認(rèn)消息,而不是一個(gè)應(yīng)用程序錯(cuò)誤。

      讓我們看一看上下文敏感異常的一個(gè)例子:

    <exception name="RecordNotFoundException">
     <messagecode>messagecode.recordnotfound</messagecode>
     <confirmationind>false</confirmationind>
     <contextind>true</contextind>
     <loggingtype>error</loggingtype>
    </exception>

      在這里,這個(gè)表達(dá)式的contextind為true。在handleException方法中傳遞的上下文可用于創(chuàng)建惟一的錯(cuò)誤代碼。例如,如果我們把order.getOrder當(dāng)作一個(gè)上下文進(jìn)行傳遞,結(jié)果得到的消息代碼就是異常的消息代碼和所傳遞的上下文的串聯(lián)。因此,我們獲得了一個(gè)像messagecode.recordnotfound.order.getOrder這樣的惟一消息代碼。

      對(duì)于每個(gè)異常來說,可以把exceptioninfo.xml 中的數(shù)據(jù)封裝到一個(gè)叫做ExceptionInfoDTO的數(shù)據(jù)傳輸對(duì)象(data transfer object,DTO)。現(xiàn)在,我們還需要一個(gè)占位符,用于緩存這些對(duì)象,因?yàn)槲覀儾幌朐诋惓3霈F(xiàn)時(shí)反復(fù)解析XML文件和創(chuàng)建對(duì)象。這項(xiàng)工作可以委托給一個(gè)叫做ExceptionInfoCache的類來完成,這個(gè)類將會(huì)在從exceptioninfo.xml文件讀取ExceptionInfoDTO對(duì)象信息之后緩存所有這些對(duì)象。

      現(xiàn)在您是否弄清楚了這整個(gè)過程?這種方法的核心部分是ExceptionHandler實(shí)現(xiàn),該實(shí)現(xiàn)將使用封裝在ExceptionInfoDTO中的數(shù)據(jù)來獲取消息代碼,創(chuàng)建ExceptionDTO對(duì)象,然后基于在給定異常的ExceptionInfoDTO中指定的日志記錄類型來記錄它。

      下面是ExceptionHandler實(shí)現(xiàn)的handleException方法:

    public ExceptionDTO handleException(String userId,BaseAppException exp) {
     ExceptionDTO exDTO = new ExceptionDTO();
     ExceptionInfoCache ecache = ExceptionInfoCache.getInstance();
     ExceptionInfo exInfo = ecache.getExceptionInfo( ExceptionHelper.getClassName(exp));
     String loggingType = null;
     if (exInfo != null) {
      loggingType = exInfo.getLoggingType();
      exDTO.setConfirmation(exInfo.isConfirmation());
      exDTO.setMessageCode(exInfo.getMessageCode());
     }

     FileLogger logger = new FileLoggerFactory().create();
     logger.logException(exp, loggingType);

      根據(jù)不同的業(yè)務(wù)需求,ExceptionHandler接口可以有多種實(shí)現(xiàn)。決定使用何種實(shí)現(xiàn)的任務(wù)可交由Factory來完成,特別是ExceptionHandlerFactory類。

      結(jié)束語

      如果缺乏全面的異常處理策略,一些特殊的異常處理塊便可能導(dǎo)致出現(xiàn)非標(biāo)準(zhǔn)的錯(cuò)誤處理和不可維護(hù)的代碼。通過使用上面的方法,便可簡(jiǎn)化J2EE應(yīng)用程序中的異常處理過程。
    posted on 2009-02-12 22:54 suprasoft Inc,. 閱讀(132) 評(píng)論(0)  編輯  收藏

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


    網(wǎng)站導(dǎo)航:
     
    ©2005-2008 Suprasoft Inc., All right reserved.
    主站蜘蛛池模板: 阿v免费在线观看| 成人免费观看男女羞羞视频| 成人永久免费高清| 精品国产免费一区二区三区| 亚洲视频在线免费观看| 成年女人男人免费视频播放| 成人免费视频一区二区| 亚洲免费一级视频| 亚洲国产一成久久精品国产成人综合| 亚洲日韩中文字幕| www.亚洲精品.com| 亚洲男人的天堂久久精品| 国产精品亚洲二区在线观看| 国产精品免费网站| 无码免费又爽又高潮喷水的视频| 色婷婷亚洲十月十月色天| 免费a在线观看播放| 曰批全过程免费视频网址| 人妻仑乱A级毛片免费看| 亚洲一区二区三区在线观看蜜桃| 中文字幕亚洲日本岛国片| 一二三四在线播放免费观看中文版视频| 一级毛片**免费看试看20分钟| 性做久久久久免费看| 亚洲人成77777在线播放网站不卡| 国产精品亚洲高清一区二区| 18禁超污无遮挡无码免费网站国产 | 美女被爆羞羞网站在免费观看| 亚洲国产精品国自产拍电影| 日韩精品亚洲专区在线观看| 免费国产作爱视频网站| 国产成人无码区免费网站| 理论片在线观看免费| 亚洲av无码片区一区二区三区| 国产亚洲美女精品久久久久狼| 国产成人综合久久精品免费| 亚洲视频免费观看| 久久久久久久岛国免费播放| a在线视频免费观看在线视频三区| 亚洲av永久无码一区二区三区| 亚洲熟妇无码爱v在线观看|