成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

高手組合 Scala整合Spring框架

開發 后端
本文將介紹如何通過將優秀的編程語言Scala整合當今世界最為流行的框架Spring中。為了清楚地闡釋Scala與Spring的整合原理,本文將使用一個簡單的示例應用。

Scala近期正式發布了2.8版本,這門優秀的編程語言將簡潔、清晰的語法與面向對象和函數式編程范式無縫融合起來,同時又完全兼容于Java,這樣Scala就能使用Java開發者所熟知的Java API和眾多的框架了。在這種情況下,我們可以通過Scala改進并簡化現有的Java框架。此外,Scala的學習門檻也非常低,因為我們可以輕松將其集成到“眾所周知的Java世界中”。

51CTO推薦專題:Scala編程語言

本文將介紹如何通過Scala整合當今世界最為流行的框架之一Spring。Spring不僅支持如依賴注入和面向方面的編程等高效的編程范式,還提供了大量的膠水代碼與Hibernate、Toplink等框架以及JEE環境交互,后者更是可以保證Scala能平滑地融入到企業當中,毫無疑問,這是Spring的成功所在。

為了清楚地闡釋Scala與Spring的整合原理,本文將使用一個簡單的示例應用。這個應用會使用到Scala、Spring和Hibernate/JPA,其領域模型如下圖所示:

 

該領域模型展示了一個簡化的社交網絡應用:人與人之間可以彼此鏈接起來。

第一步

后面的講解都將基于該領域模型。首先介紹如何實現一個泛型DAO,并通過Hibernate/JPA使用Scala為Person實體實現一個具體的DAO,該DAO的名字為PersonDao,里面封裝了CRUD操作。如下所示:

 

  1. val p1 = new Person(“Rod Johnson”)   
  2. val p2 = dao.findByName(“Martin Odersky”)   
  3. p1.link(p2)  
  4. personDao.save(p1) 

第二步

接下來介紹如何將Person實體轉換為一個“內容豐富”的領域對象,在調用link方法時,該對象內部會使用NotificationService執行額外的邏輯,這個服務會“神奇地”按需注入到對象中。下圖展示了這一切:

  1. val p1 = Person(“Martin Odersky”) //the omission of the ‘new’ keyword is intentional   
  2. val p2 = dao.findByName(“Rod Johnson”)   
  3. p1.link(p2) //magic happens here  
  4. personDao.save(p1)  

第三步

最后,本文將介紹Spring是如何從Scala的高級概念:特征(traits)中受益的。特征可以將內容豐富的Person領域對象轉換為羽翼豐滿的OO類,這個類能夠實現所有的職責,包括CRUD操作。如下所示:

  1. Person(“Martin Odersky”).save   

第一步:使用Scala、Spring和Hibernate/JPA實現DAO

需求

毫無疑問,DAO在設計上應該有一個泛型DAO和一個針對Person實體的具體DAO。泛型DAO中應該包含基本的CRUD方法,如save、remove、findById和findAll等。由于是泛型,因此它處理的是類型而不是具體的實體實現。總的來說,這個泛型DAO具有如下的接口定義:

  1. trait GenericDao[T] {  
  2.       def findAll():List[T]  
  3.       def save(entity:T):T   
  4.       def remove(entity:T):Unit  
  5.       def findById(id:Serializable):T  

Person實體類的具體DAO應該增加一個特定于Person實體的finder方法:

  1. trait PersonDao extends GenericDao[Person] {  
  2.   def findByName(name:String):List[Person]  
  3.   //more finders here…  
  4. }  

我們需要考慮如下具體的實現細節以便利用上Scala提供的眾多富有成效的特性:

◆關于集合:雖然底層的JPA實現并不知道所謂的Scala集合,但DAO接口返回的卻是Scala集合類型(scala.List)而不是Java集合。因為Scala集合要比Java集合強大的多,因此DAO方法的調用者非常希望方法能夠返回Scala集合。這樣,我們需要將JPA返回的Java集合平滑地轉換為Scala集合。

◆關于回調:Spring用于粘合JPA、JMS等框架的大多數膠水代碼都是基于模板模式,比如JpaTemplate、JmsTemplate等。雖然這些模板通過一些便捷的方法在一定程度上隱藏了底層框架的復雜性,但很多時候我們還是不可避免地要直接訪問底層的實現類,如EntityManager、JmsSession等。在這種情況下,Spring通過JpaCallback等回調類來實現我們的愿望。回調方法doIn…(..)唯一的參數就是指向實現類的引用,比如EntityManager。下面的示例闡述了這種編程模型:

  1.  
  2.  
  3. jpaTemplate.execute(new JpaCallback() {  
  4.  public Object doInJpa(EntityManager em) throws PersistenceException {  
  5.  //… do something with the EntityManager  
  6.  return null;  
  7. }  
  8. });  

上面的代碼有兩點值得我們注意:首先,匿名內部回調類的實例化需要大量的樣板代碼。其次,還有一個限制:匿名內部類JpaCallback之外的所有參數都必須是final的。如果從Scala的視角來看待這種回調模式,我們發現里面充斥的全都是某個“函數”的繁瑣實現。我們真正想要的只是能夠直接訪問EntityManager而已,并不需要匿名內部類,而且還得實現里面的doInJpa(…)方法,這有點太小題大作了。換句話說,我們只需要下面這一行足矣:

  1.  
  2.  
  3. jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. );   

問題在于如何通過優雅的方式實現這個功能。

◆關于getter和setter:使用了Spring bean的類至少要有一個setter方法,該方法對應于特定bean的名稱。毫無疑問,這些setter是框架所需的樣板代碼,如果不使用構造器注入也能避免這一點豈不美哉?

實現

如果用Scala實現泛型與Person DAO,那么上面提到的一切問題都將迎刃而解,請看:

  1. object GenericJpaDaoSupport {  
  2.    
  3.    implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {  
  4.     new JpaCallback {  
  5.       def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}  
  6.     }   
  7. }  
  8.  
  9. import Scala.collection.jcl.Conversions._  
  10. class GenericJpaDaoSupport[T](val entityClass:Class[T]) extends JpaDaoSupport with GenericDao[T] {  
  11.  
  12.       def findAll():List[T] = {   
  13.             getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]  
  14.       }  
  15.  
  16.       def save(entity:T) :T = {  
  17.         getJpaTemplate().persist(entity)  
  18.         entity  
  19.       }  
  20.  
  21.       def remove(entity:T) = {  
  22.         getJpaTemplate().remove(entity);          
  23.       }  
  24.  
  25.       def findById(id:Serializable):T = {  
  26.         getJpaTemplate().find(entityClass, id).asInstanceOf[T];  
  27.       }  
  28. }  
  29.  
  30. class JpaPersonDao extends GenericJpaDaoSupport(classOf[Person]) with PersonDao {  
  31.        
  32.         def findByName(name:String) = { getJpaTemplate().executeFind( (em:EntityManager) => {  
  33.             val query = em.createQuery("SELECT p FROM Person p WHERE p.name like :name");  
  34.             query.setParameter("name", "%" + name + "%");  
  35.             query.getResultList();  
  36.       }).asInstanceOf[List[Person]].toList  
  37.       }  
  38. }  

使用:

  1. class PersonDaoTestCase extends AbstractTransactionalDataSourceSpringContextTests {  
  2.     @BeanProperty var personDao:PersonDao = null 
  3.       
  4.     override def getConfigLocations() = Array("ctx-jpa.xml", "ctx-datasource.xml")  
  5.    
  6.     def testSavePerson {  
  7.         expect(0)(personDao.findAll().size)  
  8.         personDao.save(new Person("Rod Johnson"))  
  9.         val persons = personDao.findAll()  
  10.         expect(1)( persons size)  
  11.         assert(persons.exists(_.name ==”Rod Johnson”))  
  12.     }  

接下來解釋上面的代碼是如何解決之前遇到的那些問題的:

關于集合

Scala 2.7.x提供了一個方便的Java集合到Scala集合的轉換類,這是通過隱式轉換實現的。上面的示例將一個Java list轉換為Scala list,如下代碼所示:

導入Scala.collection.jcl.Conversions類的所有方法:

  1. import Scala.collection.jcl.Conversions._  

這個類提供了隱式的轉換方法將Java集合轉換為對應的Scala集合“包裝器”。對于java.util.List來說,Scala會創建一個Scala.collection.jcl.BufferWrapper。

調用BufferWrapper的toList()方法返回Scala.List集合的一個實例。

下面的代碼闡述了這個轉換過程:

  1. def findAll() : List[T]  = {   
  2.     getJpaTemplate().find("from " + entityClass.getName).toList.asInstanceOf[List[T]]  

總是手工調用“toList”方法來轉換集合有些麻煩。幸好,Scala 2.8(在本文撰寫之際尚未發布最終版)將會解決這個瑕疵,它可以通過scala.collection.JavaConversions類將Java轉換為Scala,整個過程完全透明。

關于回調

可以通過隱式轉換將Spring回調輕松轉換為Scala函數,如GenericJpaDaoSupport對象中所示:

  1.  
  2.  
  3. implicit def jpaCallbackWrapper[T](func:(EntityManager) => T) = {  
  4.     new JpaCallback {  
  5. def doInJpa(session:EntityManager ) = func(session).asInstanceOf[Object]}  
  6. }  

借助于這個轉換,我們可以通過一個函數來調用JpaTemplate的execute方法而無需匿名內部類JPACallback了,這樣就能直接與感興趣的對象打交道了:

  1. jpaTemplate.execute((em:EntityManager) => em.createQuery(…)// etc. ); 

這么做消除了另一處樣板代碼。

關于getter和setter

默認情況下,Scala編譯器并不會生成符合JavaBean約定的getter和setter方法。然而,可以通過在實例變量上使用Scala注解來生成JavaBean風格的getter和setter方法。下面的示例取自上文的PersonDaoTestCase:

  1. import reflect._  
  2. @BeanProperty var personDao:PersonDao = _ 

@BeanProperty注解告訴Scala編譯器生成setPersonDao(…)和getPersonDao()方法,而這正是Spring進行依賴注入所需的。這個簡單的想法能為每個實例變量省掉3~6行的setter與getter方法代碼。

第二步:按需進行依賴注入的富領域對象

到目前為止,我們精簡了DAO模式的實現,該實現只能持久化實體的狀態。實體本身并沒有什么,它只維護了一個狀態而已。對于領域驅動設計(DDD)的擁躉來說,這種簡單的實體并不足以應對復雜領域的挑戰。一個實體若想成為富領域對象不僅要包含狀態,還得能調用業務服務。為了達成這一目標,需要一種透明的機制將服務注入到領域對象中,不管對象在何處實例化都該如此。

Scala與Spring的整合可以在運行期輕松將服務透明地注入到各種對象中。后面將會提到,這種機制的技術基礎是DDD,可以用一種優雅的方式將實體提升為富領域對象。

需求

為了說清楚何謂按需的依賴注入,我們為這個示例應用加一個新需求:在調用Person實體的link方法時,它不僅會鏈接相應的Person,還會調用NotificationService以通知鏈接的雙方。下面的代碼闡述了這個新需求:

  1. class Person   
  2. {     @BeanProperty var notificationService:NotificationService = _    def link(relation:Person) =   
  3. {       relations.add(relation)       notificationService.nofity(PersonLinkageNotification(this, relation))      
  4. }     //other code omitted for readability   
  5.   }    

毫無疑問,在實例化完Person實體或從數據庫中取出Person實體后就應該可以使用NotificationService了,無需手工設置。

使用Spring實現自動裝配  

我們使用Spring的自動裝配來實現這個功能,這是通過Java單例類RichDomainObjectFactory達成的:

  1.  public class RichDomainObjectFactory implements BeanFactoryAware   
  2. {         
  3. pritic RichDomainObjectFactory singleton = new   
  4. RichDomainObjectFactory();              
  5. public static RichDomainObjectFactory autoWireFactory()   
  6. {           
  7.  return singleton;        
  8. }   
  9. public void autowire(Object instance)   
  10. {            
  11. factory.autowireBeanProperties(instance)  
  12. }         
  13. public void setBeanFactory(BeanFactory factory) throws BeansException {            
  14. this.factory = (AutowireCapableBeanFactory) factory;       
  15.  }         
  16.  }   

通過將RichDomainObjectFactory聲明為Spring bean,Spring容器確保在容器初始化完畢后就設定好了AutowireCapableBeanFactory:

  1. <bean class="org.jsi.di.spring.RichDomainObjectFactory" factory-method="autoWireFactory"/> 

這里并沒有讓Spring容器創建自己的RichDomainObjectFactory實例,而是在bean定義中使用了factory-method屬性,它會強制Spring使用autoWireFactory()方法返回的引用,該引用是單例的。這樣會將AutowireCapableBeanFactory注入到單例的RichDomainObjectFactory中。由于可以在同一個類裝載器范圍內訪問單例對象,這樣該范圍內的所有類都可以使用RichDomainObjectFactory了,它能以一種非侵入、松耦合的方式使用Spring的自動裝配特性。毋庸置疑,Scala代碼也可以訪問到RichDomainObjectFactory單例并使用其自動裝配功能。

在設定完這個自動裝配工廠后,接下來需要在代碼/框架中定義鉤子(hook)了。總的來說需要在兩個地方定義:

◆ORM層,它負責從數據庫中加載實體

◆需要“手工”創建新實體的代碼中

自動裝配ORM層中的領域對象

由于文中的示例代碼使用了JPA/Hibernate,因此在實體加載后需要將這些框架所提供的設備掛載到RichDomainObjectFactory中。JPA/Hibernate提供了一個攔截器API,這樣可以攔截和定制實體加載等事件。為了自動裝配剛加載的實體,需要使用如下的攔截器實現:

  1. class DependencyInjectionInterceptor extends EmptyInterceptor {  
  2.     override def onLoad(instance:Object, id:Serializable, propertieValues:Array[Object],propertyNames:Array[String], propertyTypes:Array[Type]) = {  
  3.       RichDomainObjectFactory.autoWireFactory.autowire(instance)  
  4.       false  
  5.    }  

該攔截器需要做的唯一一件事就是將加載的實體傳遞給RichDomainObjectFactory的autowire方法。對于該示例應用來說,onLoad方法的實現保證了每次從數據庫中加載Person實體后都將NotificationService注入其中。

此外,還需要通過hibernate.ejb.interceptor屬性將攔截器注冊到JPA的持久性上下文中:

  1. <persistence-unit name="ScalaSpringIntegration" transaction-type="RESOURCE_LOCAL"> 
  2.      <provider>org.hibernate.ejb.HibernatePersistence</provider> 
  3.      <property name="hibernate.ejb.interceptor"                             value="org.jsi.domain.jpa.DependencyInjectionInterceptor" />   
  4.      </properties> 
  5.      <!-- more properties here--> 
  6. </persistence-unit> 

DependencyInjectionInterceptor非常強大,每次從數據庫中加載實體后它都能將在Spring中配置的服務注入其中。那如果我們在應用代碼而非JAP等框架中實例化實體時又該怎么辦呢?

自動裝配“手工”實例化的領域對象

要想自動裝配應用代碼中實例化的實體,最簡單也是最笨的辦法就是通過RichDomainObjectFactory的方式顯式進行自動裝配。由于這個辦法將RichDomainObjectFactory類與實體創建代碼緊耦合起來,因此并不推薦使用。幸好,Scala提供了“組件對象”的概念,它擔負起工廠的職責,可以靈活實現構造邏輯。

對于該示例應用,我們采用如下方式實現Person對象以便“自動”提供自動裝配功能:

  1. import org.jsi.di.spring.RichDomainObjectFactory._  
  2. object Person {  
  3.     def apply(name:String) = {  
  4.        autoWireFactory.autowire(new Person(name))  
  5.     }  

import聲明會導入RichDomainObjectFactory的所有靜態方法,其中的autoWireFactory()方法會處理RichDomainObjectFactory單例對象。

Scala對象另一個便利的構造手段就是apply()方法,其規則是擁有apply方法的任何對象在調用時可以省略掉.apply()。這樣,Scala會將對Person()的調用轉給Person.apply(),因此可以將自動裝配代碼放到apply()方法中。

這樣,無需使用“new”關鍵字就可以調用Person()了,它會返回一個新的實體,返回前所有必要的服務都已經注入進去了,該實體也成為一個“富”DDD實體了。

現在我們可以使用富領域對象了,它是可持久化的,也能在需要時調用其中的服務:

  1. trait JpaPersistable[T] extends JpaDaoSupport  {  
  2.    def getEntity:T;  
  3.  
  4.    def findAll():List[T] = {   
  5.         getJpaTemplate().find("from " + getEntityClass.getName).toList.asInstanceOf[List[T]]     
  6.    }  
  7.  
  8.    def save():T = {  
  9.        getJpaTemplate().persist(getEntity)  
  10.        getEntity  
  11.    }  
  12.  
  13.    def remove() = {  
  14.        getJpaTemplate().remove(getEntity);          
  15.    }  
  16.         
  17.    def findById(id:Serializable):T = {  
  18.         getJpaTemplate().find(getEntityClass, id).asInstanceOf[T];  
  19.    }  
  20.    //…more code omitted for readability        
  21. }  

 

在繼續之前,我們需要解釋一下為何要用Java而不是Scala來實現RichDomainObjectFactory,原因是由Scala處理static的方式造成的。Scala故意沒有提供static關鍵字,因為static與復合的OO/函數式范式有沖突。Scala語言所提供的唯一一個靜態特性就是對象,其在Java中的等價物就是單例。由于Scala缺少static方法,因此Spring沒法通過上文介紹的factory-method屬性獲得RichDomainObjectFactory這樣的工廠對象。這樣,我們就沒法將Spring的AutowireCapableBeanFactory直接注入到Person對象中了。因此,這里使用Java而非Scala來利用Spring的自動裝配功能,它能徹底填充static鴻溝。

第三步:使用Scala traits打造功能完善的領域對象

到目前為止一切尚好,此外,Scala還為OO純粹主義者提供了更多特性。使用DAO持久化實體與純粹的OO理念有些許沖突。從廣泛使用的DAO/Repository模式的角度來說,DAO只負責執行持久化操作,而實體則只維護其狀態。但純粹的OO對象不僅有狀態,還要有行為。

上文介紹的實體是擁有服務的,這些服務封裝了一些行為性職責,但持久化部分并不在其中。為什么不把所有的行為性和狀態性職責都賦給實體呢,就像OO純粹主義者所倡導的那樣,讓實體自己負責持久化操作。事實上,這是習慣問題。但使用Java很難以優雅的方式讓實體自己去實現持久化操作。這種設計嚴重依賴于繼承,因為持久化方法要在父類中實現。這種方式相當麻煩,也缺少靈活性。Java從概念上就缺少一個良好設計的根基,沒法很好地實現這種邏輯。但Scala則不同,因為Scala有traits。

所謂trait就是可以包含實現的接口。它類似于C++中多繼承的概念,但卻沒有眾所周知的diamond syndrome副作用。通過將DAO代碼封裝到trait中,該DAO trait所提供的所有持久化方法可自動為所有實現類所用。這種方式完美地詮釋了DRY(Don’t Repeat Yourself)準則,因為持久化邏輯只實現一次,在需要的時候可以多次混合到領域類中。

對于該示例應用來說,其DAO trait如下代碼所示:

  1. trait JpaPersistable[T] extends JpaDaoSupport  {  
  2.    def getEntity:T;  
  3.  
  4.    def findAll():List[T] = {   
  5.         getJpaTemplate().find("from " + getEntityClass.getName).toList.asInstanceOf[List[T]]     
  6.    }  
  7.  
  8.    def save():T = {  
  9.        getJpaTemplate().persist(getEntity)  
  10.        getEntity  
  11.    }  
  12.  
  13.    def remove() = {  
  14.        getJpaTemplate().remove(getEntity);          
  15.    }  
  16.         
  17.    def findById(id:Serializable):T = {  
  18.         getJpaTemplate().find(getEntityClass, id).asInstanceOf[T];  
  19.    }  
  20.    //…more code omitted for readability        
  21. }  

作為一個傳統的DAO,該trait繼承了Spring的JpaDaoSupport,但它并沒有提供save、update和delete方法(這些方法需要接收一個實體作為參數)轉而定義了一個抽象方法getEntity,需要持久化功能的領域對象得實現這個方法。JpaPersistable trait在內部實現中使用getEntity來保存、更新和刪除特定的實體,如下代碼片段所示。

  1. trait JpaPersistable[T] extends JpaDaoSupport  {  
  2. def getEntity:T  
  3.         
  4. def remove() = {  
  5.    getJpaTemplate().remove(getEntity);          
  6. }  
  7. //…more code omitted for readability  

實現該trait的領域對象只需實現getEntity方法即可,該方法的實現僅僅是返回一個自身引用:

  1. class Person extends JpaPersistable[Person] with java.io.Serializable {  
  2.  
  3.   def getEntity = this 
  4.   //…more code omitted for readability  
  5. }  

這就是全部了。所有需要持久化行為的領域對象只需實現JpaPersistable trait即可。最后我們得到的是一個包含了狀態和行為功能完善的領域對象,完全符合純粹的OO編程的理念:

  1. Person(“Martin Odersky”).save  

無論你是否為純粹的OO理念的擁護者,這個示例都闡釋了Scala(尤其是traits概念)是如何輕松實現純粹的OO設計的。

結論

本文示例介紹了Scala與Spring是如何實現互補的。Scala簡明、強大的范式(比如函數與特征)再結合Spring的依賴注入、AOP和Java AP為我們I提供了更廣闊的空間,相對于Java代碼來說,Scala的實現更具表現力、代碼量也更少。
如果具有Spring和Java基礎,Scala的學習曲線非常低,因為我們只需要學習一門新語言就行,無需再學大量的API了。

Scala和Spring所提供的眾多功能使得這一組合成為企業采用Scala的最佳選擇。總之,我們能以極低的代價遷移到更加強大的編程范式上來。

關于作者

Urs Peter是Xebia的高級咨詢師,專注于企業級Java和敏捷開發。它有9年的IT從業經歷。在整個IT職業生涯中,他擔任過不同角色,從開發者、軟件架構師到Scrum Master。目前,他在下一代的荷蘭鐵路信息系統項目中擔任Scrum Master,該項目部分使用Scala實現。他還是Xebia的一名Scala布道師和荷蘭Scala用戶組的活躍分子。

文中所用源代碼

感興趣的讀者可以使用git:git clone git://github.com/upeter/Scala-Spring-Integration.git在http://github.com/upeter/Scala-Spring-Integration上下載完整的源代碼并使用maven構建。

查看英文原文:Scala & Spring: Combine the best of both worlds

【編輯推薦】

  1. 編程思想碰撞 Scala不是改良的Java
  2. Scala 2.8最終發布 全新功能值得期待
  3. Scala vs F#:函數式編程特性大比拼(一)
  4. Scala vs F#:函數式編程特性大比拼(二)
  5. 用Java在各種框架下編譯Scala項目

 

責任編輯:佚名 來源: InfoQ
相關推薦

2009-07-22 09:02:45

Scala組合繼承

2009-07-21 10:04:57

Scala編程語言

2018-08-15 10:51:01

JavaSpring MVC框架

2009-07-08 16:10:24

Scala簡介面向對象函數式

2009-06-19 10:00:37

Struts和Spri

2016-12-14 09:03:34

springhibernate異常

2021-11-10 11:37:48

Spring整合 Mybatis

2009-06-18 15:24:08

Spring OSGi

2011-05-19 09:52:48

SSH

2009-07-17 17:16:48

Spring iBAT

2009-07-14 14:41:33

Webwork與Spr

2009-07-14 16:55:32

MyEclipse S

2009-06-01 10:28:03

SpringOSGi整合

2009-06-25 17:13:51

jBPM與Spring

2022-06-07 07:58:45

SpringSpring AOP

2013-06-13 10:36:22

JavaEE開發框架

2010-06-21 17:08:10

Java框架ScalaSpring

2022-05-30 09:32:07

Spring容器

2025-07-02 07:33:02

Spring倒排索引分布式

2009-08-11 09:47:01

Spring整合Str
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天堂中文在线观看 | 日韩美av | 一区二区三区在线免费观看 | 成人免费大片黄在线播放 | www.99热| 毛片a级 | 婷婷综合久久 | 日韩中字幕 | 草草在线观看 | 依人成人 | 亚洲国产成人精品久久久国产成人一区 | 亚洲国产欧美国产综合一区 | 国产成人亚洲精品 | 精品无码久久久久国产 | 在线观看中文字幕视频 | 特级黄一级播放 | 少妇一区在线观看 | 亚洲视频免费 | 国产69久久精品成人看动漫 | 国产一区二区激情视频 | 欧美日韩精品一区 | 欧美一区二区三区 | 亚洲国产一区二区三区在线观看 | 国产精品久久一区二区三区 | 久热精品视频 | 在线观看黄色电影 | 999久久久久久久久6666 | 日本三级视频 | 在线中文字幕第一页 | 久久黄色网 | 天天爽夜夜骑 | 99精品99| 成年人在线观看视频 | 精品国产精品三级精品av网址 | 亚洲精品女人久久久 | 亚洲精品18| 韩国电影久久 | 久夜精品 | 国产精品中文字幕一区二区三区 | 欧美一级在线 | 欧美日韩在线一区二区 |