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

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

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

    隨筆 - 19, 文章 - 93, 評論 - 17, 引用 - 0
    數(shù)據(jù)加載中……

    使用Spring JMS輕松實現(xiàn)異步消息傳遞

    ??????異步進(jìn)程通信是面向服務(wù)架構(gòu)(SOA)一個重要的組成部分,因為企業(yè)里很多系統(tǒng)通信,特別是與外部組織間的通信,實質(zhì)上都是異步的。Java消息服務(wù)(JMS)是用于編寫使用異步消息傳遞的JEE應(yīng)用程序的API。傳統(tǒng)的使用JMS API進(jìn)行消息傳遞的實現(xiàn)包括多個步驟,例如JNDI查詢隊列連接工廠和Queue資源,在實際發(fā)送和接收消息前創(chuàng)建一個JMS會話。

       Spring框架則簡化了使用JEE組件(包括JMS)的任務(wù)。它提供的模板機(jī)制隱藏了典型的JMS實現(xiàn)的細(xì)節(jié),這樣開發(fā)人員可以集中精力放在處理消息的實際工作中,而不用擔(dān)心如何去創(chuàng)建,訪問或清除JMS資源。

       本文將對Spring JMS API作一個概述,并通過一個運(yùn)行在JBoss MQ服務(wù)器上的web例程來介紹如何使用Spring JMS API來異步處理(發(fā)送和接收)消息。我將通過傳統(tǒng)JMS實現(xiàn)和Spring JMS實現(xiàn)兩者間的比較,來展示使用Spring JMS處理消息是如何的簡單和靈活。

    異步消息傳遞和面向服務(wù)架構(gòu)

      在現(xiàn)實中,大多數(shù)web請求都是同步處理的。例如,當(dāng)用戶要登入一個網(wǎng)站,首先輸入用戶名和密碼,然后服務(wù)器驗證登錄合法性。如果驗證成功,程序?qū)⒃试S該用戶進(jìn)入網(wǎng)站。這里,登錄請求在從客戶端接收以后被即時處理了。信用卡驗證是另一個同步處理的例子;只有服務(wù)器證實輸入的信用卡號是有效的,同時客戶在帳戶上有足夠的存款,客戶才被允許繼續(xù)操作。但是讓我們思考一下在順序處理系統(tǒng)上的支付結(jié)算步驟。一旦系統(tǒng)證實該用戶信用卡的信息是準(zhǔn)確的,并且在帳戶上有足夠的資金,就不必等到所有的支付細(xì)節(jié)落實、轉(zhuǎn)賬完成。支付結(jié)算可以異步方式進(jìn)行,這樣客戶可以繼續(xù)進(jìn)行核查操作。

       需要比典型同步請求耗費(fèi)更長時間的請求,可以使用異步處理。另一個異步處理的例子是,在本地貸款處理程序中,提交至自動承銷系統(tǒng)(AUS)的信用請求處理過程。當(dāng)借方提交貸款申請后,抵押公司會向AUS發(fā)送請求,以獲取信用歷史記錄。由于這個請求要求得到全面而又詳細(xì)的信用報告,包括借方現(xiàn)今和過去的帳戶,最近的付款和其他財務(wù)資料,服務(wù)器需要耗費(fèi)較長的時間(幾小時或著有時甚至是幾天)來對這些請求作出響應(yīng)??蛻舳顺绦颍☉?yīng)用)要與服務(wù)器連接并耗費(fèi)如此長的時間來等待結(jié)果,這是毫無意義的。因此通信應(yīng)該是異步發(fā)生的;也就是,一旦請求被提交,它就被放置在隊列中,同時客戶端與服務(wù)器斷開連接。然后AUS服務(wù)從指定的隊列中選出請求進(jìn)行處理,并將處理得到的消息放置在另一個消息隊列里。最后,客戶端程序從這個隊列中選出處理結(jié)果,緊接著處理這個信用歷史數(shù)據(jù)。

    JMS

       如果您使用過JMS代碼,您會發(fā)現(xiàn)它與JDBC或JCA很像。它所包含的樣本代碼創(chuàng)建或JMS資源對象回溯,使得每一次您需要寫一個新類來發(fā)送和接收消息時,都具有更好的代碼密集性和重復(fù)性。以下序列顯示了傳統(tǒng)JMS實現(xiàn)所包括的步驟:

    1. 創(chuàng)建JNDI初始上下文(context)。
    2. 從JNDI上下文獲取一個隊列連接工廠。
    3. 從隊列連接工廠中獲取一個Quene。
    4. 創(chuàng)建一個Session對象。
    5. 創(chuàng)建一個發(fā)送者(sender)或接收者(receiver)對象。
    6. 使用步驟5創(chuàng)建的發(fā)送者或接收者對象發(fā)送或接收消息。
    7. 處理完消息后,關(guān)閉所有JMS資源。
    您可以看到,步驟6是處理消息的唯一地方。其他步驟都只是管理與實際業(yè)務(wù)要求無關(guān)的JMS資源,但是開發(fā)人員必須編寫并維護(hù)這些額外步驟的代碼。

    ?

    Spring JMS

       Spring框架提供了一個模板機(jī)制來隱藏Java APIs的細(xì)節(jié)。JEE開發(fā)人員可以使用JDBCTemplate和JNDITemplate類來分別訪問后臺數(shù)據(jù)庫和JEE資源(數(shù)據(jù)源,連接池)。JMS也不例外。Spring提供JMSTemplate類,因此開發(fā)人員不用為一個JMS實現(xiàn)去編寫樣本代碼。接下來是在開發(fā)JMS應(yīng)用程序時Spring所具有一些的優(yōu)勢。

    1. 提供JMS抽象API,簡化了訪問目標(biāo)(隊列或主題)和向指定目標(biāo)發(fā)布消息時JMS的使用。
    2. JEE開發(fā)人員不需要關(guān)心JMS不同版本(例如JMS 1.0.2與JMS 1.1)之間的差異。
    3. 開發(fā)人員不必專門處理JMS異常,因為Spring為所有JMS異常提供了一個未經(jīng)檢查的異常,并在JMS代碼中重新拋出。
    一旦您在JMS應(yīng)用程序中開始使用Spring,您將會欣賞到它在處理異步消息傳遞上的簡便。Spring JMS框架提供多種Java類,可以輕松實現(xiàn)JMS應(yīng)用。表1列出了這些類的一部分。

    ?

       表1. Spring JMS類

    類名功能
    JmsExceptionorg.springframework.jms只要發(fā)生一個JMS異常,Spring框架就會拋出異常,這個類是這些所拋出的異常的基(抽象)類。
    JmsTemplate, JmsTemplate102org.springframework.jms.core這些是輔助類,用于簡化JMS的使用,處理JMS資源(如連接工廠,目標(biāo)和發(fā)送者/接收者對象)的創(chuàng)建和釋放。JmsTemplate102是JmsTemplate的子類,使用JMS1.0.2規(guī)范
    MessageCreatororg.springframework.jms.core這是JmsTemplate類使用的回叫接口,它為指定的會話創(chuàng)建JMS消息。
    MessageConverterorg.springframework.jms.support.converter這個接口充當(dāng)一個抽象,用來在Java對象與JMS消息之間進(jìn)行轉(zhuǎn)換。
    DestinationResolverorg.springframework.jms.support.destination這是JmsTemplate用來解析目標(biāo)名的接口。該接口的默認(rèn)實現(xiàn)是DynamicDestinationResolver和JndiDestinationResolve

      在接下來的部分,我將詳細(xì)解釋表1所列的一部分類(例如JmsTemplate,DestinationResolver和MessageConverter)。

    JMSTemplate

      JmsTemplate提供了幾種輔助方法,用來執(zhí)行一些基本操作。要開始使用JmsTemplate前,您需要知道JMS供應(yīng)商支持哪個JMS規(guī)范,JBoss AS 4.0.2WebLogic 8.1服務(wù)器支持JMS 1.0.2規(guī)范。WebLogic Server 9.0包括了對JMS 1.1規(guī)范的支持。JMS 1.1統(tǒng)一了點(diǎn)對點(diǎn)(PTP)和發(fā)布/訂閱(Pub/Sub)域的編程接口。這種改變的結(jié)果就是,開發(fā)人員可以創(chuàng)建一個事務(wù)會話,然后在這同一個JMS會話里,可以從一個Queue(PTP)中接收消息,同時發(fā)送另一個消息到一個Topic(Pub/Sub)。JMS 1.1向后兼容JMS 1.0,應(yīng)此根據(jù)JMS 1.0編寫的代碼仍可以適用于JMS 1.1。

       JmsTemplate提供多種發(fā)送和接收消息的方法。表2列出了這些方法的一部分。

       表2. JMS template方法

    方法名稱功能
    send發(fā)送消息至默認(rèn)或指定的目標(biāo)。JmsTemplate包含send方法,它通過javax.jms.Destination或JNDI查詢來指定目標(biāo)。
    receive從默認(rèn)或指定的目標(biāo)接收消息,但只會在指定的時間后傳遞消息。我們可以通過receiveTimeout屬性指定超時時間。
    convertAndSend這個方法委托MessageConverter接口實例處理轉(zhuǎn)換過程,然后發(fā)送消息至指定的目標(biāo)。
    receiveAndConvert從默認(rèn)或指定的目標(biāo)接收消息。并將消息轉(zhuǎn)換為Java對象。

      目標(biāo)可以通過JNDI上下文保存和獲取。當(dāng)配置Spring程序上下文(application context)時,我們可以用JndiObjectFactoryBean類取得對JMS的引用。DestinationResolver接口是用來把目標(biāo)名稱解析成JMS目標(biāo),當(dāng)應(yīng)用程序存在大量目標(biāo)時,這是非常有用的。DynamicDestinationResolver(DestinationResolver的默認(rèn)實現(xiàn))是用來解析動態(tài)目標(biāo)的。

       MessageConverter接口定義了將Java對象轉(zhuǎn)換為JMS消息的約定。通過這個轉(zhuǎn)換器,應(yīng)用程序代碼可以集中于處理事務(wù)對象,而不用為對象如何表示為JMS消息這樣的內(nèi)部細(xì)節(jié)所困饒。SimpleMessageConverter(和SimpleMessageConverter102)是MessageConverter的默認(rèn)實現(xiàn)??墒褂盟鼈兎謩e將String轉(zhuǎn)換為JMS TextMessage,字節(jié)數(shù)組(byte[])轉(zhuǎn)換為JMS BytesMessage,Map轉(zhuǎn)換為JMS MapMessage,和Serializable對象轉(zhuǎn)換為JMS ObjectMessage。您也可以編寫自定義的MessageConverter實例,通過XML綁定框架(例如JAXB, Castor,Commons Digester,XMLBeansXStream),來實現(xiàn)XML文檔到TextMessage對象的轉(zhuǎn)換。

    示例程序

      我將用一個貸款申請?zhí)幚硐到y(tǒng)(命名為LoanProc)示例來演示如何在JMS應(yīng)用程序中使用Spring。作為貸款申請的一部分,LoanProc通過發(fā)送貸款詳情(貸款I(lǐng)D,借方名字,借方的SSN,貸款期限和貸款數(shù)額),從AUS系統(tǒng)獲得信用歷史詳情。為了簡便起見,我們基于兩個基本參數(shù)來表示信用歷史詳情:信用分?jǐn)?shù)(又名FICO得分)和貸款數(shù)額。讓我們假設(shè)處理信用檢查請求是按以下業(yè)務(wù)規(guī)則進(jìn)行的:

    1. 如果貸款數(shù)額等于或低于,000,借方必須至少有一個"好"的信用(也就是,借方的FICO得分在680到699之間)。
    2. 如果貸款數(shù)額高于,000,借方必須至少有"很好"的信用,意味著借方的信用得分要高于700。

    貸款申請使用案例

      信用請求處理使用案例包括以下幾個步驟:

    1. 用戶在貸款申請頁面輸入貸款詳情并提交貸款申請。
    2. 發(fā)送請求到一個名為CreditRequestSendQueue的消息隊列。然后程序發(fā)送貸款詳情到AUS系統(tǒng),獲取信用歷史詳情。
    3. AUS系統(tǒng)從隊列中挑出貸款詳情,并使用貸款參數(shù)從它的數(shù)據(jù)庫中獲取信用歷史信息。
    4. 然后AUS將找到的借方的信用歷史信息創(chuàng)建一個新的消息,發(fā)送到一個新的名為CreditRequestReceiveQueue的消息隊列。
    5. 最后,LoanProc從接收隊列中選出響應(yīng)消息,處理貸款申請來決定是否批準(zhǔn)或否決申請。

      在這個例程中,兩個消息隊列都配置在同一個JBoss MQ server上。使用案例用圖1的序列圖(SequenceDiagram)表示

    序列圖

    圖1.貸款處理程序的序列圖 (單擊截圖來查看完整視圖)

       下面的表3顯示了在例程中我所使用的不同技術(shù)和開源框架,并按應(yīng)用邏輯層排列。

       表3. 在JMS應(yīng)用程序中使用的框架

    邏輯層技術(shù)/框架
    MVCSpring MVC
    ServiceSpring Framework (version 2.1)
    JMS APISpring JMS
    JMS ProviderJBoss MQ (version 4.0.2)
    JMS ConsoleHermes
    IDEEclipse 3.1

    使用Hermes設(shè)置JMS資源

      為了異步處理消息,首先我們需要消息隊列發(fā)送和接收消息。我們可以用Jboss里的配置XML文件創(chuàng)建一個新的消息隊列,然后使用JMS控制臺瀏覽隊列的詳細(xì)情況。清單1顯示了配置JMS的XML配置代碼片斷(這個應(yīng)該加入到j(luò)bossmq-destinations-service.xml文件,位于%JBOSS_HOME%server lldeploy-hasingletonjm文件夾下。)

       清單1.JBoss MQ Server上JMS隊列的配置

    <!--  Credit Request Send Queue  -->
    <mbean code="org.jboss.mq.server.jmx.Queue"
      name="jboss.mq.destination:service=Queue,name=CreditRequestSendQueue">
      <depends optional-attribute-name="DestinationManager">
     jboss.mq:service=DestinationManager
      </depends>
    </mbean>
    
    <!--  Credit Request Receive Queue  -->
    <mbean code="org.jboss.mq.server.jmx.Queue"
      name="jboss.mq.destination:service=Queue,name=CreditRequestReceiveQueue">
      <depends optional-attribute-name="DestinationManager">
     jboss.mq:service=DestinationManager
      </depends>
    </mbean>
    

      現(xiàn)在,讓我們看看如何使用一個名為Hermes的JMS工具來瀏覽消息隊列。Hermes是一個Java Swing應(yīng)用程序,它可以創(chuàng)建、管理和監(jiān)視JMS提供商(例如JBossMQ,WebSphereMQ,ActiveMQArjuna服務(wù)器)里的JMS目標(biāo)。從它的網(wǎng)站上下載Hermes,解壓縮.zip文件到本地目錄(例如,c:dev oolshermes)。一旦安裝完成,雙擊文件hermes.bat(位于bin文件夾下)啟動程序。

       要在Hermes里配置JBossMQ服務(wù)器,請參考Hermes網(wǎng)站上的這個演示。它有著出色的step-by-step可視化指示來配置JBoss MQ。當(dāng)配置一個新的JNDI初始上下文時,請輸入下面的信息。

    • providerURL = jnp://localhost:1099
    • initialContextFactory = org.jnp.interfaces.NamingContextFactory
    • urlPkgPrefixes = org.jnp.interfaces:org.jboss.naming
    • securityCredentials = admin
    • securityPrincipal = admin

      當(dāng)您創(chuàng)建新的目標(biāo)時,請輸入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。圖2顯示了JMS控制臺的主窗口,其中有為JMS例程創(chuàng)建的新的消息隊列。

    Hermes中所有目標(biāo)的截圖

    圖 2. Hermes中所有目標(biāo)的截圖.(單擊截圖來查看完整視圖)

       下面的圖3顯示了在從消息發(fā)送者類發(fā)送消息到CreditRequestSendQueue后,Hermes JMS控制臺及消息隊列的截圖。您可以看見有5個消息在隊列中,控制臺顯示了消息詳情,例如消息ID,消息目標(biāo),時間戳和實際的消息內(nèi)容。

    Hermes中所有隊列的截圖

    圖 3. Hermes中所有隊列的截圖.(單擊截圖來查看完整視圖)

       在例程中使用的隊列名稱和其他JMS和JNDI參數(shù)見表 4。

       表4. Spring JMS配置參數(shù)

    參數(shù)名稱參數(shù)值
    Initial Context Factoryorg.jnp.interfaces.NamingContextFactory
    Provider URLlocalhost:8080
    Initial Context Factory URL Packagesorg.jnp.interfaces:org.jboss.naming
    Queue Connection FactoryUIL2ConnectionFactory
    Queue Namequeue/CreditRequestSendQueue, queue/CreditRequestReceiveQueue

    Spring配置

      既然我們已經(jīng)有了運(yùn)行例程所需要的JMS目標(biāo),現(xiàn)在該了解用XML Spring配置文件(名為spring-jms.xml)來組配JMS組件的具體細(xì)節(jié)了。這些組件是根據(jù)Inversion of Controller (IOC)設(shè)計模式里的設(shè)置方式注入原則(setter injection principle),用JMS對象實例類組配的。讓我們詳細(xì)查看這些組件,并為每一個JMS組件演示一段XML配置代碼。

       JNDI上下文是取得JMS資源的起始位置,因此首先我們要配置JNDI模板。清單2顯示了名為jndiTemplate的Spring bean,其中列有JNDI初始上下文所必需的常用參數(shù)。

       清單2. JNDI上下文模板

    <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">
                    org.jnp.interfaces.NamingContextFactory
                </prop>
                <prop key="java.naming.provider.url">
                    localhost
                </prop>
                <prop key="java.naming.factory.url.pkgs">
                    org.jnp.interfaces:org.jboss.naming
                </prop>
            </props>
        </property>
    </bean>
    

      接著,我們配置隊列連接工廠。清單3顯示了隊列連接工廠的配置。

       清單3. JMS隊列連接工廠配置

    <bean id="jmsQueueConnectionFactory"
          class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate">
            <ref bean="jndiTemplate"/>
        </property>
        <property name="jndiName">
            <value>UIL2ConnectionFactory</value>
        </property>
    </bean>
    

      我們定義2個JMS目標(biāo)來發(fā)送和接收消息。詳情見清單4和5。

       清單4. 發(fā)送隊列配置

    <bean id="sendDestination"
        class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate">
            <ref bean="jndiTemplate"/>
        </property>
        <property name="jndiName">
            <value>queue/CreditRequestSendQueue</value>
        </property>
    </bean>
    

      清單5. 接收隊列配置

    <bean id="receiveDestination"
        class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate">
            <ref bean="jndiTemplate"/>
        </property>
        <property name="jndiName">
            <value>queue/CreditReqeustReceiveQueue</value>
        </property>
    </bean>
    

      然后我們再來配置JmsTemplate組件。在例程中我們使用JmsTemplate102。同時使用defaultDestination屬性來指定JMS目標(biāo)。

       清單6. JMS模板配置

    <bean id="jmsTemplate" 
          class="org.springframework.jms.core.JmsTemplate102">
        <property name="connectionFactory">
            <ref bean="jmsQueueConnectionFactory"/>
        </property>
        <property name="defaultDestination">
            <ref bean="destination"/>
        </property>
        <property name="receiveTimeout">
            <value>30000</value>
        </property>
    </bean>
    

      最后我們配置發(fā)送者和接收者組件。清單7和8分別是Sender 和 Receiver對象的配置。

       清單7. JMS Sender配置

    <bean id="jmsSender" class="springexample.client.JMSSender">
        <property name="jmsTemplate">
            <ref bean="jmsTemplate"/>
        </property>
    </bean>
    

      清單8. JMS Receiver配置

    <bean id="jmsReceiver" class="springexample.client.JMSReceiver">
        <property name="jmsTemplate">
            <ref bean="jmsTemplate"/>
        </property>
    </bean>
    

    測試及監(jiān)視

      我寫了一個測試類,命名為LoanApplicationControllerTest,用來測試LoanProc程序。我們可以使用這個類來設(shè)定貸款參數(shù)以及調(diào)用信用請求服務(wù)類。

       讓我們看一下不使用Spring JMS API而使用傳統(tǒng)JMS開發(fā)途徑的消息發(fā)送者實例。清單9顯示了MessageSenderJMS類里的sendMessage方法,其中包含了使用JMS API處理消息的所有必需步驟。

       清單9. 傳統(tǒng)JMS實例

    public void sendMessage() {
    
        queueName = "queue/CreditRequestSendQueue";
        System.out.println("Queue name is " + queueName);
    
        /*
         * Create JNDI Initial Context
         */
        try {
            Hashtable env = new Hashtable();
            env.put("java.naming.factory.initial",
                "org.jnp.interfaces.NamingContextFactory");
            env.put("java.naming.provider.url","localhost");
            env.put("java.naming.factory.url.pkgs",
                "org.jnp.interfaces:org.jboss.naming");
    
            jndiContext = new InitialContext(env);
        } catch (NamingException e) {
            System.out.println("Could not create JNDI API " +
                "context: " + e.toString());
        }
    
        /*
         * Get queue connection factory and queue objects from JNDI context.
         */
        try {
            queueConnectionFactory = (QueueConnectionFactory)
            jndiContext.lookup("UIL2ConnectionFactory");
    
            queue = (Queue) jndiContext.lookup(queueName);
        } catch (NamingException e) {
            System.out.println("JNDI API lookup failed: " +
                e.toString());
        }
    
        /*
         * Create connection, session, sender objects.
         * Send the message.
         * Cleanup JMS connection.
         */
        try {
            queueConnection =
                queueConnectionFactory.createQueueConnection();
            queueSession = queueConnection.createQueueSession(false,
                    Session.AUTO_ACKNOWLEDGE);
            queueSender = queueSession.createSender(queue);
            message = queueSession.createTextMessage();
            message.setText("This is a sample JMS message.");
            System.out.println("Sending message: " + message.getText());
            queueSender.send(message);
    
        } catch (JMSException e) {
            System.out.println("Exception occurred: " + e.toString());
        } finally {
            if (queueConnection != null) {
                try {
                    queueConnection.close();
                } catch (JMSException e) {}
            }
        }
    }
    

      現(xiàn)在,我們來看看使用了Spring的消息發(fā)送者實例。清單10顯示了MessageSenderSpringJMS類中send方法的代碼。

       清單10. 使用Spring API的JMS實例

    public void send() {
        try {
            ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {
                    "spring-jms.xml"});
    
            System.out.println("Classpath loaded");
    
            JMSSender jmsSender = (JMSSender)appContext.getBean("jmsSender");
    
            jmsSender.sendMesage();
    
            System.out.println("Message sent using Spring JMS.");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    

      如您所見,通過使用配置文件,所有與管理JMS資源有關(guān)的步驟都將交由Spring容器處理。我們只需引用一個JMSSender對象,然后調(diào)用對象里的sendMessage方法。

    結(jié)束語

      在本文中,我們看到Spring框架是如何使用JMS API簡化異步消息傳遞。Spring去掉了所有使用JMS處理消息所必需的樣本代碼(例如得到一個隊列連接工廠,從Java代碼里創(chuàng)建隊列和會話對象,在運(yùn)行時使用配置文件對它們進(jìn)行組配)。我們可以動態(tài)的交換JMS資源對象,而不必修改任何Java代碼,這要感謝Inversion of Control (IOC) 原則的力量。

      既然異步消息傳遞是SOA框架的整體構(gòu)成部分,Spring很適合納入到SOA工具集。此外,JMS管理工具(如Hermes)使得創(chuàng)建、管理和監(jiān)督JMS資源變得容易,特別是對于系統(tǒng)管理員來說。

    參考資料

    原文出處:http://www.onjava.com/pub/a/onjava/2006/02/22/asynchronous-messaging-with-spring-jms.html

    posted on 2006-12-03 10:11 BPM 閱讀(836) 評論(0)  編輯  收藏 所屬分類: JMS


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


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 国产精品九九久久免费视频 | 亚洲免费中文字幕| 免费无码H肉动漫在线观看麻豆| 国产自偷亚洲精品页65页| 亚洲日韩精品国产一区二区三区 | 亚洲一区二区三区影院| 一级毛片高清免费播放| 亚洲欧洲日本在线| 国产99视频精品免费视频76| 日日噜噜噜噜夜夜爽亚洲精品| aa级毛片毛片免费观看久| 久久国产亚洲精品麻豆| 蜜臀98精品国产免费观看| 精品久久洲久久久久护士免费| 亚洲av综合色区| 精品无码无人网站免费视频| 亚洲精品综合久久中文字幕| 真人做人试看60分钟免费视频| 亚洲熟妇成人精品一区| 国产精品久久久久影院免费| 美女视频黄频a免费观看| 国产成人免费网站| 亚洲啪AV永久无码精品放毛片| 在线视频免费国产成人| 亚洲中文字幕无码久久2020| 国产又长又粗又爽免费视频| 天黑黑影院在线观看视频高清免费| 亚洲a一级免费视频| 国产午夜免费高清久久影院| 亚洲精品国产福利片| 国产成人在线免费观看| 国内精品免费久久影院| 亚洲日本乱码一区二区在线二产线| 蜜桃精品免费久久久久影院| 久久精品免费大片国产大片| 亚洲专区一路线二| 亚洲国产精品一区二区九九| 在线看无码的免费网站| 老司机午夜精品视频在线观看免费| 亚洲va无码va在线va天堂| 色婷婷7777免费视频在线观看|