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

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

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

    posts - 4,comments - 30,trackbacks - 0

    Spring 快速入門教程──開發你的第一個Spring程序

    本章學習用struts MVC框架作前端,Spring做中間層,Hibernate作后端來開發一個
    簡單的Spring應用程序。在第4章將使用Spring MVC框架對它進行重構。

    本章包含以下內容:
    編寫功能性測試。
    配置Hibernate和Transaction。
    載入Spring的applicationContext.xml文件。
    設置業務代理(business delegates)和DAO的依賴性。
    把spring寫入Struts 程序。

    概述

    你將會創建一個簡單的程序完成最基本的CRUD(Create, Retrieve, Update和 Delete)
    操作。這個程序叫MyUsers,作為本書的樣例。這是一個3層架構的web程序,通過一個
    Action 來調用業務代理,再通過它來回調DAO類。下面的流程圖表示了MyUsers是如何工
    作的。數字表明了流程的先后順序,從web層(UserAction)到中間層(UserManager),再到
    數據層(UserDAO),然后返回。


    鑒于大多數讀者都比較熟悉struts,本程序采用它作為MVC 框架。Spring的魅力在于
    它宣稱式的事務處理,依懶性的綁定和持久性的支持。第4 章中將用Spring框架對它進行
    重構。

    接下來你會進行以下幾個步驟:

    1.下載Struts和Spring。
    2.創建項目目錄和ant Build文件。
    3.為持久層創建一個單元測試(unit tests)。
    4.配置Hibernate和Spring。
    5.編寫HIbernate DAO的實現。
    6.進行單元測試,通過DAO驗證CRUD。
    7.創建一個Manager來聲明事務處理。
    8.為struts Action 編寫測試程序。
    9.為web層創建一個Action 和model(DynaActionForm)。
    10.進行單元測試,通過Action驗證CRUD。
    11.創建JSP頁面,以通過瀏覽器來進行CRUD操作。
    12.通過瀏覽器來驗證JSP頁面的功能。
    13.用velocity模板替換JSP頁面。
    14.使用Commons Validator添加驗證。

    下載Struts和Spring

    1.下載安裝以下組件:
    .
    JDK1.4.2(或以上)
    .
    Tomcat5.0+
    .
    Ant 1.6.1+
    2.設置以下環境變量:
    .
    JAVA_HOME
    .
    ANT_HOME
    .
    CATALINA_HOME
    3.把以下路徑添加到PATH中:
    .
    JAVA_HOME/bin
    .
    ANT_HOME/bin
    .
    CATALINA_HOME/bin

    為了開發基于java的web項目,開發人員必須事先下載必需的jars,建好開發目錄結
    構和ant build文件。對于單一的struts項目,可以利用struts中現成的strutsblank.war。對于基于Spring MVC 框架的項目,可以用Spring中的webapp-minimal.war。
    這只為開發作準備,兩者都沒有進行struts-spring集成,也沒有考慮單元測試。為此,我
    們為讀者準備了Equinox。Equinox為開發Struts-spring式的程序提供一個基本框架。它已經定義好了目錄結構,
    和ant build文件(針對compiling,deploying,testing),并且提供了struts, spring,
    Hibernate開發要用到的jars文件。Equinox中大部分目錄結構和ant build文件來自我的
    開源項目──AppFuse??梢哉f,Equinox是一個簡化的AppFuse,它在最小配置情況下,為
    快速web開發提供了便利。由于Equinox源于AppFuse,所以在包名,數據庫名,及其它地方
    都找到它的影子。這是為讓你從基于Equinox的程序過渡到更為復雜的AppFuse。

    http://sourcebeat.com/downloads上下載Equinox, 解壓到一個合適的位置,開始
    準備MyUsers的開發。


    創建項目目錄和ant build文件

    為了設置初始的目錄結構,把下載的Equinox解壓到硬盤。建議windows用戶把項目放
    在C:\Source,Unix/Linux用戶放在~/dev(譯注:在當前用戶目錄建一個dev目錄)中。
    windows用戶可以設置一個HOME環境變量,值為C:\Source。最簡單的方法是把Equinox解
    壓到你的喜歡的地方,進入equinox目錄,運行ant new -Dapp.anme=myusers。

    tips:在windows使用cygwin(http://www.cygwin.org/)就可以像Unix/Linux系統一樣使用正
    斜杠,本書所有路徑均采用正斜杠。其它使用反斜杠系統(如windows中命令行窗口)的用戶
    請作相應的調整。

    現在MyUsers程序已經有如下的目錄結構:


    Equinox包含一個簡單而功能強大的build.xml,它可以用ant來進行編譯,布署,和測

    試。ant中已經定義好targets,在equinox運行ant,將看到如下內容:
    [echo] Available targets are:
    [echo] compile --> Compile all Java files
    [echo] war --> Package as WAR file
    [echo] deploy --> Deploy application as directory
    [echo] deploywar --> Deploy application as a WAR file
    [echo] install --> Install application in Tomcat
    [echo] remove --> Remove application from Tomcat
    [echo] reload --> Reload application in Tomcat
    [echo] start --> Start Tomcat application
    [echo] stop --> Stop Tomcat application
    [echo] list --> List Tomcat applications


    [echo] clean --> Deletes compiled classes and WAR
    [echo] new --> Creates a new project


    Equinox支持tomcat的ant tasks(任務)。這些已經集成在Equinox中,解講一下如何
    進行集成的有助于理解它們的工作原理。

    Tomcat 和ant

    tomcat中定義了一組任務,可以通過Manager來安裝(install),刪除(remove),重載
    (reload)webapps。要使用這些任務,可以把所有的定義寫在一個屬性文件中。在Eqinox的
    根目錄下,有一個名為tomcatTasks.properties包含如下內容。

    deploy=org.apache.catalina.ant.DeployTask
    undeploy=org.apache.catalina.ant.UndeployTask
    remove=org.apache.catalina.ant.RemoveTask
    reload=org.apache.catalina.ant.ReloadTask
    start=org.apache.catalina.ant.StartTask
    stop=org.apache.catalina.ant.StopTask
    list=org.apache.catalina.ant.ListTask


    在build.xml定義一些任務來安裝,刪除,重新載入應用程序
    。
    <!-- Tomcat Ant Tasks --
    >
    <taskdef
    file="tomcatTasks.properties"
    >


    <classpath>
    <pathelement path="${tomcat.home}/server/lib/catalina-ant.jar"/
    >


    </classpath>
    </taskdef>
    <target name="install" description="Install application in Tomcat"

    depends="war">

    <deploy url="${tomcat.manager.url}" username="${tomcat.manager.username}
    "
    password="${tomcat.manager.password}" path="/${webapp.name}" war="file:
    $
    {dist.dir}/${webapp.name}.war"/>

    </target>
    <target name="remove" description="Remove application from Tomcat">
    <undeploy url="${tomcat.manager.url}" username="${tomcat.manager.username}
    "

    password="${tomcat.manager.password}" path="/${webapp.name}"/>


    </target>
    <target name="reload" description="Reload application in Tomcat">
    <reload url="${tomcat.manager.url}" username="${tomcat.manager.username}
    "

    password="${tomcat.manager.password}" path="/${webapp.name}"/>
    </target>
    <target name="start" description="Start Tomcat application">
    <start url="${tomcat.manager.url}" username="${tomcat.manager.username}
    "

    password="${tomcat.manager.password}" path="/${webapp.name}"/>
    </target> <target name="stop" description="Stop Tomcat application">
    <stop url="${tomcat.manager.url}" username="${tomcat.manager.username}
    "

    password="${tomcat.manager.password}" path="/${webapp.name}"/>
    </target>
    <target name="list" description="List Tomcat applications">
    <list url="${tomcat.manager.url}
    "

    username="${tomcat.manager.username}
    "
    password="${tomcat.manager.password}"/
    >
    </target>


    在上面列出的target中,必須定義一些${tomcat.*}變量。在根目錄下有一個

    build.properties默認定義如下:
    # Properties for Tomcat Server
    tomcat.manager.url=http://localhost:8080/manager
    tomcat.manager.username=admin
    tomcat.manager.password=admin

    確保admin用戶可以訪問Manager應用,打開$CATALINA_HOME/conf/tomcatusers.xml中是否存在下面一行。如果不存在,請自己添加。注意,roles 屬性可能是一個
    以逗號(“,”)隔開的系列。

    <user username="admin" password="admin" roles="manager"/>

    為了測試所有修改,保存所有文件,啟動tomcat。從命令行中進行myusers目錄,運
    行ant list,可以看到 tomcat server上運行的應用程序。


    好了,現在運行 ant deploy來安裝MyUsers。打開瀏覽器,在地址欄中輸入
    http://localhost:8080/myusers, 出現如圖2.4的“Equinox Welcome”畫面。


    下一節,將寫一個User對象和一個維護其持久性的Hibernate DAO對象。用Sping來
    管理DAO 類及其依賴性。最會寫一個業務代理,用到AOP和聲明式事務處理。


    為持久層編寫單元測試

    在myUsers 程序,使用Hibernat 作為持久層。Hinbernate 是一個O/R 映像框架,用來關
    聯java 對象和數據庫中的表(tables) 。它使得對象的CRUD 操作變得非常簡單,Spring 結合了
    Hibernate 變得更加容易。從Hibernate 轉向Spring+Hibernate 會減少75% 的代碼。這主要是因
    為,ServiceLocater 和一些DAOFactory 類的廢棄,spring 的實時異常代替了Hibernate 的檢測
    式的異常。

    寫一個單元測試有助于規范UserDAO 接口。為UserDAO 寫一個JUint 測試程序,要完
    成以下幾步:

    1.在test/org/appfuse/dao 下新建一個UserDAOTest.java 類。它繼承了同一個包中的
    BaseDAOTestCase,其父類初始化了Spring 的ApplictionContext( 來自web/WEBINF/applictionContext.xml) ,以下是JUnit 測試的代碼。
    package org.appfuse.dao;

    // use your IDE to handle imports

    public class UserDAOTest extends BaseDAOTestCase {

    private User user = null;

    private UserDAO dao = null;

    protected void setUp() throws Exception {

    log = LogFactory.getLog(UserDAOTest.class);

    dao = (UserDAO) ctx.getBean("userDAO");

    }

    protected void tearDown() throws Exception {

    dao = null;

    }

    public static void main(String[] args) {

    junit.textui.TestRunner.run(UserDAOTest.class)
    ;
    }


    }

    這個類還不能編譯,因為還沒有UserDAO 接口。在這之前,來寫一些來驗證User 的
    CRUD 操作。

    2.為UserDAOTest 類添加testSave 和testAddAndRemove 方法,如下:
    public void testSaveUser() throws Exception {


    user = new User()
    ;
    user.setFirstName("Rod")
    ;
    user.setLastName("Johnson")
    ;
    dao.saveUser(user)
    ;
    assertTrue("primary key assigned", user.getId() != null)
    ;
    log.info(user)
    ;
    assertTrue(user.getFirstName() != null)
    ;


    }
    public void testAddAndRemoveUser() throws Exception
    {


    ?user = new User()
    ;
    user.setFirstName("Bill")
    ;
    user.setLastName("Joy")
    ;
    dao.saveUser(user)
    ;
    assertTrue(user.getId() != null)
    ;
    assertTrue(user.getFirstName().equals("Bill"))
    ;
    if (log.isDebugEnabled())
    {


    log.debug("removing user...")
    ;
    }
    dao.removeUser(user.getId())
    ;
    assertNull(dao.getUser(user.getId()))
    ;


    }

    從這些方法中可以看到,你需要在UserDAO 創建以下方法

    saveUser(User)
    removeUser(Long)
    getUser(Long)
    getUsers() ( 返回數據庫的所有用戶
    )


    3.在src/org/appfuse/dao 目錄下建一個名為UserDAO.java 類的,輸入以下代碼:
    tips:如果你使用eclipse,idea 之類的IDE,左邊會出現在一個燈泡,提示類不存在,可以
    即時創建。

    package org.appfuse.dao;
    // use your IDE to handle imports
    public interface UserDAO extends DAO
    {
    public List getUsers()
    ;
    public User getUser(Long userId)
    ;



    public void saveUser(User user);
    public void removeUser(Long userId);
    }
    為了UserDAO.java,UserDAOTest.java 編譯通過,還要建一個User.java 類。

    4.在src/org/appfuse/model 下建一個User.java 文件,添加幾個成員變量:
    id,firstName,lastName ,如下。

    package org.appfuse.model;

    public class User extends BaseObject {

    private Long id;

    private String firstName;

    private String lastName;

    /* 用你熟悉的IDE 來生成getters 和setters,Eclipse 中右擊> Source -> Generate Getters
    and Setters */

    }

    注意,你繼承了BaseObject 類,它包含幾個有用的方法:toString(),equlas(),hasCode(),

    后兩個是Hibernate 必須的。建好User 后,用IDE 打開UserDAO 和UserDAOTest 兩個類,
    優化導入。


    配置Hibernate 和Spring

    現在已經有了POJO(Plain Old Java Object), 寫一個映像文件Hibernate 就可能維護它。

    1.在org/appfuse/model 中新建一個名為User.hbm.xml 文件,內容如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "<hibernate-mapping>
    <class name="org.appfuse.model.User" table="app_user"
    >
    <id name="id" column="id" unsaved-value="0"
    >


    ?<generator class="increment" /
    >
    </id>
    <property name="firstName" column="first_name" not-null="true"/
    >
    <property name="lastName" column="last_name" not-null="true"/
    >
    </class>
    </hibernate-mapping>


    2.在web/WEB-INF/ 下的applictionContext.xml 中添加映像。打開文件,找到<property
    name= mappingResources >,改成如下:
    <property name="mappingResources">
    <list>
    <value>org/appfuse/model/User.hbm.xml</value>
    </list>
    </property>


    在applictionContext.xml 中,你可以看到數據庫是怎幺工作的,Hibernate 和Spring 是如
    何協作的。Eqinox 會使用名為db/appfuse 的HSQL 數據庫。它將在你的ant “db” 目錄下創建,
    詳細配置在“How Spring Is Configured in Equinox ” 一節中描述。

    3.運行ant deploy reload(Tomcat 正在運行),在Tomcat 控制臺的日志中可以看到,數據
    表正在創建。
    INFO - SchemaExport.execute(98) | Running hbm2ddl schema export

    INFO - SchemaExport.execute(117) | exporting generated schema to database

    INFO - ConnectionProviderFactory.newConnectionProvider(53) | Initializing connection


    provider: org.springframework.orm.hibernate.LocalDataSourceConnectionProvider
    INFO - DriverManagerDataSource.getConnectionFromDriverManager(140) | Creating new

    JDBC connection to [jdbc:hsqldb:db/appfuse]
    INFO - SchemaExport.execute(160) | schema export complete
    Tip: 如果你想看到更多或更少的日志,修改web/WEB-INF/ classes/log4j.xml 的設置。

    4.為了驗證數據庫已經建好,運行 ant browser 啟動hsql console 。你會看到如的HSQL
    Database Manager 。
    Equinox 中spring 是怎幺配置的

    使用Spring 配置任何基于j2ee 的web 程序都很簡單。至少,你簡單的添加Spring 的
    ContextLoaderListener 到你的web.xml 中。
    <listener>
    <listener-class>
    org.springframework.web.context.ContextLoaderListener

    </listener-class>

    </listener>

    這是一個ServletContextListener ,它會在啟動web 應用進行初始化。默認情況下,它會


    查找web/WEB-INF/applictionContext.xml 文件,你可以指定名為contextConfigLocation 的
    <context-param> 元素來進行修改,例如:

    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/sampleContext.xml</param-value>


    </context-param>

    <param-value> 元素可以是以空格或是逗號隔開的一系列路徑。在 Equnox 中,Spring
    的配置使用了這個Listener 和默認的 contextConfigLocation 。
    那幺,Spring 怎幺知道Hibernate 的存在?這就Spring 的魅力所在,它讓依賴性的綁定
    變得非常簡單。請參閱applicationContext.xml 的全部內容:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "
    <beans>
    <bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName">

    <value>org.hsqldb.jdbcDriver</value>
    </property>
    <property name="url"
    >


    <value>jdbc:hsqldb:db/appfuse</value>
    </property>
    <property name="username"
    >


    <value>sa</value>
    </property>
    <property name="password"
    >


    <value></value>
    </property> </bean>
    <!-- Hibernate SessionFactory --
    >
    <bean id="sessionFactory"


    class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
    <property name="dataSource">
    <ref local="dataSource"/>


    </property>
    <property name="mappingResources"
    >
    <list>


    <value>org/appfuse/model/User.hbm.xml</value>
    </list>
    </property>
    <property name="hibernateProperties"
    >
    <props>


    <prop key="hibernate.dialect"> net.sf.hibernate.dialect.HSQLDialect </prop>

    <prop key="hibernate.hbm2ddl.auto">create</prop>
    </props>
    </property>
    </bean>
    <!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->

    <bean id="transactionManager"
    class="org.springframework.orm.hibernate.HibernateTransactionManager">
    <property name="sessionFactory">
    <ref local="sessionFactory"/>

    </property>
    </bean>
    </beans>


    第一bean 代表HSQL 數據庫,Spring 僅僅是調用LocalSessionFactoryBeanr 的
    setDataSource(DataSource) 使之工作。如果你想用JNDI DataSource 替換,可以bean 的定義改
    成類似下面的幾行:

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName">
    <value>java:comp/env/jdbc/appfuse</value>
    </property>
    </bean>

    hibernate.hbm2ddl.auto 屬性在sessionFactory 定義中,這個屬性是為了在應用啟動時自
    動創建表,也可能是update 或create-drop 。
    最后一個bean 是transactionManager( 你也可以使用JTA transaction) ,在處理跨越兩個


    數據庫的分布式的事務處理中尤其重要。如果你想使用jta transaction manager ,將此bean 的
    class 屬性改成

    org.springframework.transaction.jta.JtaTransactionManager

    下面實現UserDAO 類。


    用hibernate 實現UserDAO

    為了實現Hibernate UserDAO ,需要完成以下幾步:

    1.在src/org/appfuse/dao/hibernate( 你要新建這個目錄/包) 新建一個文件
    UserDAOHibernate.ava 。這個類繼承了HibernatDaoSupport 類,并實現了UserDAO 接口。
    package org.appfuse.dao.hibernate;
    // use your IDE to handle imports
    public class UserDAOHibernate extends HibernateDaoSupport implements UserDAO
    {
    private Log log = LogFactory.getLog(UserDAOHibernate.class)
    ;


    public List getUsers()
    {
    return getHibernateTemplate().find("from User")
    ;
    }


    public User getUser(Long id)
    {
    return (User) getHibernateTemplate().get(User.class, id)
    ;
    }


    public void saveUser(User user)
    {
    getHibernateTemplate().saveOrUpdate(user)
    ;
    if (log.isDebugEnabled())
    {


    log.debug( userId set to: + user.getID())
    ;
    }
    }


    public void removeUser(Long id)
    {
    Object user = getHibernateTemplate().load(User.class, id)
    ;
    getHibernateTemplate().delete(user)
    ;
    }


    }

    Spring 的HibernateDaoSupport 類是一個方便實現Hibernate DAO 的超類,你可以它的


    一些有用的方法,來獲得Hibernate DAO 或是SessionFactory 。最方便的方法是
    getHibernateTemplate() ,它返回一個HibernateTempalte 對象。這個模板把檢測式異常
    (checked exception) 包裝成實時式異常(runtime exception) ,這使得你的DAO 接口無需拋出
    Hibernate 異常。

    程序還沒有把UserDAO 綁定到 UserDAOHibernate 上,必須創建它們之間的關聯。

    2.在Spring 配置文件(web/WEB-INF/applictionContext.xml) 中添加以下內容:
    <bean id="userDAO" class="org.appfuse.dao.hibernate.UserDAOHibernate"
    >
    <property name="sessionFactory"
    >
    <ref local="sessionFactory"/
    >
    </property>
    </bean>


    這樣就在你的UserDAOHibernate( 從HibernateDaoSupport 的setSessionFactory 繼承)中建
    了一個Hibernate Session Factory 。Spring 會檢測一個Session( 也就是,它在web 層是開放的)
    是否已經存在,并且直接使用它,而不是新建一個。這樣你可以使用Spring 流行的

    Open Session in View ” 模式來載入collections 。


    進行單元測試,來驗證DAO 的CRUD 操作

    在進行第一個測試之前,把你的日志級別從“INFO” 調到“WARN” 。

    1.把log4j.xml( 在web/WEB-INF/classes 目錄下)中<level value="INFO"/> 改為<level
    value="WARN"/> 。
    2.鍵入ant test 來運行UserDAOTest 。如果你有多個測試,你必須用ant test
    -Dtestcase=UserDAOTest 來指定要運行的測試。運行之后,你會如下類似的一些日志信息。

    創建Manager ,聲明事務處理(create manager and declare
    transactions)

    J2EE開發中建議將各層進行分離。換言之,不要把數據層(DAOs)和web層(servlets)
    混在一起。使用Spring,很容易做到這一點,但使用“業務代理”(business delegate)模
    式, 可以對這些層進一步分離。

    使用業務代理模式的主要原因是:


    大多數持久層組件執行一個業務邏輯單元,把邏輯放在一非web類中的最大好處是,
    web service或是豐富平臺客戶端(rich platform client)可以像使用servlet一樣
    來用同一API。

    大多數業務邏輯都在同一方法中完成,當然可能多個DAO。使用業務代理,使得你
    可以高級業務代理層(level)使用Spring的聲明式業務代理特性。
    MyUsers應用中UserManager和UserDAO擁有相同的一個方法。主要不同的是Manager
    對于web更為友好(web-friendly),它可以接受Strings,而UserDAO只能接受Longs, 并且
    它可以在saveUser方法中返回一個User對象。這在插入一個新用戶比如,要獲得主鍵,是
    非常方便的。Manager(或稱為業務代理)中也可以添加一些應用中所需要的其它業務邏輯。

    1.啟動“service”層,在test/org/appfuse/service(你必須先建好這個目錄)中新建
    一個UserManagerTest類,這個類繼承了JUnit 的 TestCase類,代碼如下:
    package org.appfuse.service;
    // use your IDE to handle imports
    public class UserManagerTest extends TestCase
    {
    private static Log log = LogFactory.getLog(UserManagerTest.class)
    ;
    private ApplicationContext ctx;
    private User user;
    private UserManager mgr;


    protected void setUp() throws Exception
    {

    String[] paths
    =
    {"/WEB-INF/applicationContext.xml"}
    ;

    ctx = new ClassPathXmlApplicationContext(paths)
    ;

    mgr = (UserManager) ctx.getBean("userManager")
    ;

    }


    protected void tearDown() throws Exception
    {
    user = null;
    mgr = null;


    }

    // add testXXX methods here
    public static void main(String[] args)
    {
    junit.textui.TestRunner.run(UserDAOTest.class)
    ;
    }


    在setup方法中,使用ClassPathXmlApplicationContext把applicationContext.xml
    載入變量ApplicationContext中。載入ApplictionContext有幾種途徑,從classpath中,
    文件系統,或web 應用內。這些方法將在第三章( The BeanFactory and How It Works.)
    中描述。

    2.輸入第一個測試方法的代碼,來驗證Manager成功完成添加或是刪除User對象

    public void testAddAndRemoveUser() throws Exception
    {
    user = new User()
    ;
    user.setFirstName("Easter")
    ;
    user.setLastName("Bunny")
    ;
    user = mgr.saveUser(user)
    ;
    assertTrue(user.getId() != null)
    ;
    if (log.isDebugEnabled())
    {
    log.debug("removing user...")
    ;
    }


    String userId = user.getId().toString()
    ;
    mgr.removeUser(userId)
    ;
    user = mgr.getUser(userId)
    ;
    if (user != null)
    {


    fail("User object found in database!")
    ;
    }
    }


    這個測試實際上是一個集成測試(integration test),而不是單元測試(unit test)。


    為了更接近單元測試,可以使用EasyMock或是類似工具來“偽裝”(fake) DAO。這樣,就
    不必關心ApplictionContext和任何依賴Spring API 的東西。建議在測試項目依賴
    (Hibernate,Spring,自己的類)的內部構件,包括數據庫。第9章,討論重構
    UserManagerTest,使用mock解決DAO的依賴性。

    3.為了編譯UserManagerTest,在src/org/appfuse/service中新建一個接口─
    ─UserManager。在org.appfuse.service包中創建這個類,代碼如下:
    package org.appfuse.service;
    // use your IDE to handle imports
    public interface UserManager
    {
    public List getUsers()
    ;


    public User getUser(String userId)
    ;
    public User saveUser(User user)
    ;
    public void removeUser(String userId)
    ;
    }


    4.建一個名為org.appfuse.service.impl的子包,新建一個類實現UserManager 接口
    的。
    package org.appfuse.service.impl;
    // use your IDE to handle imports

    public class UserManagerImpl implements UserManager
    {
    private static Log log = LogFactory.getLog(UserManagerImpl.class)
    ;
    private UserDAO dao;

    public void setUserDAO(UserDAO dao)
    {
    this.dao = dao;
    }


    public List getUsers()
    {
    return dao.getUsers()
    ;
    }


    public User getUser(String userId)
    {
    User user = dao.getUser(Long.valueOf(userId))
    ;
    if (user == null)
    {



    log.warn("UserId '" + userId + "' not found in database.")
    ;

    }
    return user;
    }


    public User saveUser(User user)
    {
    dao.saveUser(user)
    ;
    return user;
    }


    public void removeUser(String userId)
    {
    dao.removeUser(Long.valueOf(userId))
    ;
    }


    }
    這個類看不出你在使用Hibernate。當你打算把持久層轉向一種不同的技術時,這樣做
    很重要。

    這個類提供一個私有dao成員變量,和setUserDAO方法一樣。這樣能夠讓Spring能夠
    表演“依賴性綁定”魔術(perform “dependency binding” magic),把這些對象扎在一起。
    在使用mock重構這個類時,你必須在UserManager接口中添加serUserDAO 方法。

    5.在進行測試之前,配置Spring,讓getBeans返回一個UserManagerImpl類。在
    web/WEB-INF/applicationContext.xml文件中,添加以下幾行:
    <bean id="userManager" class="org.appfuse.service.UserManagerImpl">
    <property name="userDAO">
    <ref local="userDAO"/
    >
    </property>
    </bean>
    唯一的問題,你還沒有使Spring的AOP,特別是聲明式的事務處理發揮作用
    。


    6.為了達到目的,使用ProxyFactoryBean代替userManager。ProxyFactoryBean是一
    個類的不同的實現,這樣AOP 能夠解釋和覆蓋調用的方法。在事務處理中,使用
    TransactionProxyFactoryBean代替UserManagerImpl 類。在context文件中添加下面bean
    的定義:
    <bean id="userManager"
    class="org.springframework.transaction.interceptor.TransactionProxy
    FactoryBean">


    <property name="transactionManager">

    <ref local="transactionManager"/
    >
    </property>
    <property name="target"
    >


    <ref local="userManagerTarget"/
    >
    </property>
    <property name="transactionAttributes"
    >
    <props>


    <prop key="save*">PROPAGATION_REQUIRED</prop>
    <prop key="remove*">PROPAGATION_REQUIRED</prop>
    <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

    </props>
    </property>
    </bean>
    從這個xml代碼片斷中可以看出,TransactionProxyFactoryBean必須有一


    transactionManager 屬性設置和transactionAttributes 定義。

    7.讓事務處理代理服務器(Transaction Proxy)知道你要模仿的對象:
    userManagerTarget。作為新bean的一部分,把原來的userManager bean改成擁有一個
    userManagerTarget的id屬性。
    編輯applictionContext.xml添加 userManager 和 userManagerTarget 的定義后,
    運行ant test -Dtestcase=UserManager ,看看終端輸出:


    8.如果你想看看事務處理的執行和提交情況,在log4j.xml中添加
    :
    <logger name="org.springframework.transaction">
    <level value="DEBUG"/>

    <!-- INFO does nothing --
    >
    </logger>


    重新運行測試,將看到大量日志信息,如它相關的對象,事務的創建和提交等。測試
    完畢,最好刪除上面的日志定義(logger)。

    祝賀你!你已經實現了一個web應用的Spring/Hibernate后端解決方案。并且你已經
    用AOP和聲明式業務處理配置好了業務代理。了不起,自我鼓勵一下!(This is no small
    feat; give yourself a pat on the back!)


    對象struts Action 進行單元測試

    業務代理和DAO都起作用,我們看看MVC框架吸盤(sucker)的上部。停!不止這些
    吧,你可以進行C(Controller),不是V(View)。為了管理用戶建一個Struts Action,繼續進
    行驅動測試(Test-Driven)開發。

    Equinox是為Struts配置的。配置Struts需要在web.xml 中進行一些設置,并在
    web/WEB-INF下定義一個struts-config.xml文件。由于Struts開發人員比較多,這里先使
    用Struts。第4 章用Spring進行處理。你想跳過這一節,直接學習Spring MVC方法,請
    參考第4章:Spring s MVC Framework。

    在test/org/appfuse/web目錄下新建一個文件UserActionTest.java,開發你的第一
    個Struts Aciton單元測試。文件內容如下:

    package org.appfuse.web;

    // use your IDE to handle imports

    public class UserActionTest extends MockStrutsTestCase
    {

    public UserActionTest(String testName)
    {
    super(testName)
    ;
    }


    public void testExecute()
    {
    setRequestPathInfo("/user")
    ;
    addRequestParameter("id", "1")
    ;
    actionPerform()
    ;
    verifyForward("success")
    ;
    verifyNoActionErrors()
    ;
    }
    }



    為web 層創建Action 和Model(DynaActionForm)

    1.在src/org/appfuse/web下新建一個文件UserAction.java。這個擴展了
    DispatchAction,你可以花幾分鐘,在這個類中,創建CRUD方法。
    package org.appfuse.web;
    // use your IDE to handle imports
    public class UserAction extends DispatchAction
    {
    private static Log log = LogFactory.getLog(UserAction.class)
    ;

    public ActionForward execute(ActionMapping mapping,

    ?ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception{

    request.getSession().setAttribute("test", "succeeded!")
    ;
    log.debug("looking up userId:
    " + request.getParameter("id"))
    ;
    return mapping.findForward("success")
    ;
    }


    }

    2.為了配置Struts,使”/user”這個請求路徑代表其它。在web/WEB-INF/strutsconfig.xml中加入一個action-mapping。打開文件加入:
    <action path="/user" type="org.appfuse.web.UserAction">
    <forward name="success" path="/index.jsp"/
    >
    </action>


    3.執行命令ant test -Dtestcase=UserAction ,你會看到友好的“BUILD
    SUCCESSFUL”
    信息。
    4.在struts-config.xml中添加form-bean定義。對于Struts ActionForm,使用
    DynaActionForm,這是一個javabean,可以從XML定義中動態的創建。
    <form-bean name="userForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="user" type="org.appfuse.model.User"/>
    </form-bean>
    這里沒有使用具體的ActionForm,因為只使用一個User 對象的包裝器。理想情況下,

    你可以User 對象,但會失去Struts環境下的一些特性:驗證屬性(validateproperties),checkbox 復位(reset checkboxs)。后面,將演示用Spring怎幺會更加簡單,


    它可以讓你在web層使用 User對象。

    5.修改action定義,在request中使用這個form。
    <action path="/user" type="org.appfuse.web.UserAction" name="userForm"
    scope="request">
    <forward name="success" path="/index.jsp"/
    >
    </action>


    6.修改UserActionTest,測試不同的 CRU方法
    。
    public class UserActionTest extends MockStrutsTestCase
    {
    public UserActionTest(String testName)
    {
    super(testName)
    ; }
    // Adding a new user is required between tests because HSQL creates
    // an in-memory database that goes away during tests.

    public void addUser()
    {
    setRequestPathInfo("/user")
    ;
    addRequestParameter("method", "save")
    ;
    addRequestParameter("user.firstName", "Juergen")
    ;
    addRequestParameter("user.lastName", "Hoeller")
    ;
    actionPerform()
    ;
    verifyForward("list")
    ;
    verifyNoActionErrors()
    ;
    }


    public void testAddAndEdit()
    {
    addUser()
    ;
    // edit newly added user
    addRequestParameter("method", "edit")
    ;
    addRequestParameter("id", "1")
    ;
    actionPerform()
    ;
    verifyForward("edit")
    ;
    verifyNoActionErrors()
    ;
    }
    public void testAddAndDelete()
    {
    addUser()
    ;



    // delete new user
    setRequestPathInfo("/user")
    ;
    addRequestParameter("method", "delete")
    ;
    addRequestParameter("user.id", "1")
    ;
    actionPerform()
    ;
    verifyForward("list")
    ;
    verifyNoActionErrors()
    ;
    }


    public void testList()
    {
    addUser()
    ;
    setRequestPathInfo("/user")
    ;
    addRequestParameter("method", "list")
    ;
    actionPerform()
    ;
    verifyForward("list")
    ;
    verifyNoActionErrors()
    ;
    List users = (List) getRequest().getAttribute("users")
    ;
    assertNotNull(users)
    ;
    assertTrue(users.size() == 1)
    ;
    }
    }


    7.修改UserAction,這樣測試程序才能通過,并能處理(客戶端)請求。最簡單的方法
    是添加edit,save和delete方法,請確保你事先已經刪除了execute方法。下面是修改過
    的UserAction.java文件。
    public class UserAction extends DispatchAction
    {
    private static Log log = LogFactory.getLog(UserAction.class)
    ;
    private UserManager mgr = null;


    public void setUserManager(UserManager userManager)
    {
    this.mgr = userManager;
    }


    public ActionForward delete(ActionMapping mapping,
    ActionForm form,



    HttpServletRequest request,
    HttpServletResponse response) throws Exception{
    if (log.isDebugEnabled())
    {
    log.debug("entering 'delete' method...")
    ;

    }
    mgr.removeUser(request.getParameter("user.id"))
    ;
    ActionMessages messages = new ActionMessages()
    ;
    messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage


    ("user.deleted"))
    ;
    saveMessages(request, messages)
    ;
    return list(mapping, form, request, response)
    ; }

    public ActionForward edit(ActionMapping mapping,

    ?ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception
    {

    if (log.isDebugEnabled())
    {

    log.debug("entering 'edit' method...")
    ;
    }
    DynaActionForm userForm = (DynaActionForm) form;
    String userId = request.getParameter("id")
    ;
    // null userId indicates an add


    if (userId != null)
    {
    User user = mgr.getUser(userId)
    ;
    if (user == null)
    {
    ActionMessages errors = new ActionMessages()
    ;
    errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage


    ("user.missing"))
    ;
    saveErrors(request, errors)
    ;
    return mapping.findForward("list")
    ; }
    userForm.set("user", user)
    ; }


    return mapping.findForward("edit")
    ;
    }


    public ActionForward list(ActionMapping mapping,

    ?ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception
    {

    if (log.isDebugEnabled())
    {

    log.debug("entering 'list' method...")
    ;
    }
    request.setAttribute("users", mgr.getUsers())
    ;
    return mapping.findForward("list")
    ;
    }


    public ActionForward save(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception
    {

    if (log.isDebugEnabled())
    {
    log.debug("entering 'save' method...")
    ;
    }
    DynaActionForm userForm = (DynaActionForm) form;
    mgr.saveUser((User)userForm.get("user"))
    ;
    ActionMessages messages = new ActionMessages()
    ;
    messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage


    ("user.saved"))
    ;
    saveMessages(request, messages)
    ;
    return list(mapping, form, request, response)
    ; } }
    接下來,你可以修改這個類的CRUD方法。

    8.修改struts-config.xml,使用ContextLoaderPlugin來配置Spring的UserManager
    設置。要配置ContextLoaderPlugin,把下面內容添加到你的struts-config.xml中。
    <plug-in className= org.springframework.web.struts.ContextLoaderPlugIn >
    <set-property property= contextConfigLocation value= /WEB


    INF/applicationContext.xml, /WEB-INF/action-servlet.xml />

    </plug-in>

    默認情況下這個插件會載入action-servlet.xml文件。要讓Test Action 知道你的
    Manager,必須配置這個插件,如同載入applicationContext。

    9.對每個使用Spring的action,定義一個type=
    org.springframework.web.struts.DelegatingActionProxy 的action-mapping,為每個
    Spring action 聲明一個配對的Spring bean。這樣修改一下你的action mapping就能使用
    這個新類。
    10.為DispatchAction修改action mapping。
    為了讓DispatchAction運行,在mapping中添加參數parameter=
    “method” , 它表
    示(在一個URL 或是隱藏字段hidden field)要調用的方法,同時轉向(forwards)edit和
    list forwards(參考能進行CRUD操作的UserAction類).

    <action path="/user"
    type="org.springframework.web.struts.DelegatingActionProxy" name="userForm"
    scope="request" parameter="method">

    <forward name="list" path="/userList.jsp"/
    >
    <forward name="edit" path="/userForm.jsp"/
    >
    </action>


    確保web目錄下已經建好userList.jsp和userForm.jsp兩個文件。暫時不必在文件中
    寫入內容。

    11.作為插件的一部分,配置Spring,將/user bean設置成“UserManager”。在
    we/WEB-INF/action-servlet.xml中添加以下定義。
    <bean name="/user" class="org.appfuse.web.UserAction" singleton="false">
    <property name="userManager">
    <ref bean="userManager"/>
    </property>
    </bean>
    定義中,使用singleton=
    false 。這樣就為每個請求,新建一個Action,減少線程

    安全的Action需求。不管是Manager還是DAO都有成員變量,可能不需要這些屬性(默認
    singleton= true )。

    12.在message.properties上配置資源綁定。
    在userAction類中,在完成一些操作后,會顯示“成功”或是“錯誤”頁面,這些信
    息的鍵可以存放在這個應用的ResourceBundle(或messages.properties文件中)。特別是
    :



    user.saved

    user.missing

    user.deleted
    把這些鍵存入web/WEB-INF/下的messages.properties文件中。例如

    user.saved=User has been saved successfully.
    user.missing=No user found with this id.
    user.deleted=User successfully deleted.


    這個文件通過struts-config.xml中的<message-resources>元素進行加載
    。
    <message-resources parameter="messages"/
    >
    運行 ant test -Dtestcase=UserAction. 輸出結果如下

    ?


    填充JSP 文件,這樣可以通過瀏覽器來進行CRUD 操作

    1.在你的jsp文件(userFrom.jsp 和userList.jsp)中添加代碼,這樣它們可以表示
    actions的結果。如果還事先準備,在web目錄下建一個文件 userList.jsp。添加一些代碼
    你就可以看到數據庫中所有的用戶資料。在下面代碼中,第一行包含(include)了一個文件
    taglibs.jsp。這個文件包含了應用所有JSP Tag Library的聲明。大部分是StrutsTag,JSTL和SiteMesh(用來美化JSP頁面)。
    <%@ include file="/taglibs.jsp"%
    >
    <title>MyUsers ~ User List</title>
    <button onclick="location.href='user.do?method=edit'">Add User</button>


    <table class="list"
    >
    <thead>


    <tr>
    <th>User Id</th>
    <th>First Name</th>
    <th>Last Name</th>


    </tr>
    </thead>
    <tbody>
    <c:forEach var="user" items="${users}" varStatus="status"
    >
    <c:choose>
    <c:when test="${status.count % 2 == 0}"
    >


    <tr class="even"
    >
    </c:when>
    <c:otherwise>


    <tr class="odd">
    </c:otherwise>
    </c:choose>
    <td><a href="user.do?method=edit&amp;id=${user.id}">${user.id}</a></ td>

    <td>${user.firstName}</td>
    <td>${user.lastName}</td>
    </tr>
    </c:forEach>
    </tbody>
    </table>



    你可以有一行“標題頭”(headings)(在<thead>中)。JSTL 的 <c:forEach>進行結果
    迭代,顯示所有的用戶。

    2.向數據庫添加一些數據,你就會看到一些真實(actual)的用戶(users)。你可以選擇
    一種方法,手工添加,使用ant browse,或是在build.xml中添加如下的target:
    <target name="populate">

    <echo message="Loading sample data..."/>

    <sql driver="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:db/appfuse"

    userid="sa" password="">
    <classpath refid="classpath"/>
    INSERT INTO app_user (id, first_name, last_name) values (5, 'Julie',
    'Raible')
    ;
    INSERT INTO app_user (id, first_name, last_name) values (6, 'Abbie',
    'Raible')
    ;
    </sql>
    </target>

    警告:為了使內置的HSQLDB正常工作,從能運行ant的目錄下啟動tomcat。在
    Unix/Linux鍵入 $CATALINA_HOME/bin/startup.sh ,在win上 %CATALINA_HOME%
    \bin\startup.bat 。


    通過瀏覽器驗證JSP 的功能

    1.有了這個JSP文件和里面的樣例數據,就可以通過瀏覽器來查看這個頁面。運行antdeploy reload,轉到地址http://localhost:8080/myusers/user.do?method=list。出現以
    下畫面:
    2.這個樣例中,缺少國際化的頁面標題頭,和列標題頭(column headings)。在
    web/WEB-INF/classes中messages.properties中加入一些鍵:
    user.id=User Id
    user.firstName=First Name
    user.lastName=Last Name

    修改過的國際化的標題頭如下

    <thead>
    <tr>


    <th><bean:message key= user.id /></th>
    <th><bean:message key= user.firstName /></th>
    <th><bean:message key= user.lastName /></th>


    </tr>
    </thead>


    注意同樣可以使用JSTL的<fmt:message key= ... >標簽。如果想為表添加排序和分


    布功能,可以使用 Display Tag (http://displaytag.sf.net/)。下面是使用這個標簽的一
    個樣例:

    <display:table name="users" pagesize="10" styleClass="list"
    requestURI="user.do?method=list">

    <display:column property="id" paramId="id" paramProperty="id"
    href="user.do?method=edit" sort="true"/>

    <display:column property="firstName" sort="true"/>

    <display:column property="lastName" sort="true"/>

    </display:table>

    請參考display tag文檔中有關的列標題頭國際化的部分。

    3.你已經建好了顯示(list),創建form就可以添加/編輯(add/edit)數據。如果事先沒
    有準備,可以在web目錄下新建一個userForm.jsp文件。向文件中添加以下代碼:

    <%@ include file="/taglibs.jsp"%>

    <title>MyUsers ~ User Details</title>

    <p>Please fill in user's information below:</p>

    <html:form action="/user"
    focus="user.firstName">

    <input type="hidden" name="method" value="save"/>

    <html:hidden property="user.id"/>

    <table>

    <tr>

    <th><bean:message key="user.firstName"/>: </th>

    <td><html:text property="user.firstName"/></td>

    </tr>

    <tr>

    <th><bean:message key="user.lastName"/>: </th>

    <td><html:text property="user.lastName"/></td>

    </tr>

    <tr>

    <td></td>

    <td> <html:submit styleClass="button">Save</html:submit>

    <c:if test="${not empty param.id}">

    <html:submit styleClass="button" onclick="this.form.method.value='delete'">

    Delete</html:submit>
    </c:if>
    </td>


    </table>

    </html:form>

    注意:如果你正在開發一個國際化的應用,把上面的信息和按鈕標簽替換成
    <bean:message> 或是 <fmt:message> 標簽。這是一個很好的練習。對于信息message,建
    議把key 名稱寫成”pageName.message”(例如:userForm.message )的形式,按鈕名字寫
    成“button.name”(例如button.save)。

    4.運行ant deploy ,通過瀏覽器頁面的user form來進行 CRUD 操作。
    最后大部分web應用都需要驗證。下一節中,配置struts validator,要求用戶的
    last name 是必填的。


    用commons Validator 添加驗證

    為了在Struts中使用驗證,執行以下幾步:

    1.在struts-config.xml中添加ValidatorPlugin。
    2.創建validation.xml,指定lastName為必填字段。
    3.僅為save()方法設置驗證(validation)。
    4.在message.properties中添加validation errors。
    在struts-config.xml 中添加ValidatorPlugin

    配置Validatorp plugins,添加以下片斷到struts-config.xml(緊接著Spring
    plugin)
    :
    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames" value="/WEB-INF/validator

    rules.xml, /WEB-INF/validation.xml"/>

    </plug-in>

    從這里你可以看出,Validator會查找WEB-INF下的兩個文件validator-ruls.xml和
    validation.xml。第一個文件,validator-rules.xml,是一個標準文件,作為Struts的一
    部分發布,它定義了所有可用的驗證器(validators),功能和客戶端的javascript類似。
    第二個文件,包含針對每個 form的驗證規則。

    創建validation.xml,指定lastName 為必填字段

    validation.xml文件中包含很多DTD定義的標準元素。但你只需要如下所示的<form>
    和<field>,更多信息請參閱Validator的文檔。在web/WEB-INF/validation.xml中的formvalidation標簽之間添加form-set元素。

    <formset>

    <form name="userForm">

    <field property="user.lastName" depends="required">

    </form>

    </formset>

    把DynaActionForm 改為DynaValidatorForm

    把struts-config.xml中的DynaActionForm 改為 DynaValidatorForm。


    <form-bean name="userForm"
    type="org.apache.struts.validator.DynaValidatorForm">

    為save() 方法設置驗證(validation)

    使用Struts DispatchAction 弊端是,驗證會在映射層(mapping level)激活。為了在
    list和edit頁面關閉驗證。你必須單獨建一個”validate=false”的映射。例如,
    AppFuse 的UserAction 有兩個映射:”/editUser”
    和”/listUser”。然而有一個更簡單
    的方法,可以減少xml ,只是多了一些java 代碼。

    1.在/user 映射中,添加validate=false 。
    2.修改UserAction 中的save() 方法,調用form.validate() 方法,如果發現錯誤,返回編輯
    頁面。
    if (log.isDebugEnabled())
    {
    log.debug("entering 'save' method...")
    ;
    }
    // run validation rules on this form


    ActionMessages errors = form.validate(mapping, request)
    ;
    if (!errors.isEmpty())
    {
    saveErrors(request, errors)
    ;
    return mapping.findForward("edit")
    ;


    }
    DynaActionForm userForm = (DynaActionForm) form;
    當dispatchAction 運行時,與附帶一個屬性的兩個映射相比,這樣更加簡潔。但用兩個

    映射也有一些優點:


    驗證失敗時,可以指定轉向”input” 屬性。

    在映射中可以添加“role” 屬性,可以指定誰有訪問權限。例如,任何人都可以看到
    編輯(edit) 頁面,但只有管理員可以保存(save) 。
    運行ant deploy 重新載入(reload),嘗試添加一個新用戶,不要填寫lastName 。你會看到
    一個驗證錯誤,表明lastName 是必填字段,如下所示:



    Struts Validator的另一種比較好的特性是客戶端驗證(client-side validation)。

    4.在form標簽(web/userForm.jsp)中添加”onsubmit”屬性,在form末尾添加
    <html:javascript>。
    <html:form action="/user"
    focus="user.firstName" onsubmit="return
    validateUserForm(this)">

    ...

    </html:form>

    <html:javascript formName="userForm"/>

    現在如果運行ant deploy,試圖保存一個lastname為空的用戶,會彈出一個
    JavaScript提示:“Last Name is required”。這里有一個問題,這個帶JavaScript的
    form 把validator的JavaScript功能都載入了頁面。再好的方法是,從外部文件導入
    Javascript。參見第5章。

    恭喜你!你已經開發一個web應用,它包含數據庫交互,驗證實現,成功信息和錯誤
    信息的顯示。第4 章,將會把這個轉向Spring 框架。第5章中,會添加異常處理,文件上
    傳,郵件發送等特性。第6章會看一下JSP的替代品,在第7章,會添加 DAO的不同實現,
    包括iBATIS, JDO 和Spring 的JDBC。

    ?

    posted on 2006-09-07 09:44 蠻哥♂楓 閱讀(1655) 評論(0)  編輯  收藏 所屬分類: Java
    主站蜘蛛池模板: 777成影片免费观看| 天天影视色香欲综合免费| 日韩精品久久久久久免费| 免费A级毛片无码无遮挡内射| 免费a级毛片网站| 色婷婷六月亚洲婷婷丁香| 精品久久久久久久久亚洲偷窥女厕| 韩日电影在线播放免费版| 成人黄页网站免费观看大全| 青青草原亚洲视频| 亚洲一区在线视频观看| 中国精品一级毛片免费播放| 无码免费午夜福利片在线| 亚洲人成网站在线播放vr| 亚洲精品蜜夜内射| 久久免费福利视频| gogo全球高清大胆亚洲| 亚洲婷婷在线视频| 一级成人生活片免费看| 亚洲国产精品免费观看| 亚洲中文字幕无码日韩| 亚洲国产精品18久久久久久| 免费女人高潮流视频在线观看| 免费午夜爽爽爽WWW视频十八禁 | 亚洲美国产亚洲AV| 免费人成在线观看网站品爱网| 亚洲美女高清一区二区三区| 亚洲男人天堂2022| 久久久久久AV无码免费网站下载 | 国产片免费福利片永久| 亚洲一区二区三区电影| 一级女人18片毛片免费视频| 成年女人毛片免费播放人| 久久亚洲免费视频| 国产精品青草视频免费播放| 成人毛片免费网站| 亚洲美女人黄网成人女| 亚洲国产精品lv| 久久精品无码免费不卡| 国产一区在线观看免费| 亚洲一区二区无码偷拍|