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。可以说,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" 
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> 
<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" 
"http://www.springframework.org/dtd/spring-beans.dtd"> 
<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&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 
蛮哥♂枫 阅读(1667) 
评论(0)  编辑  收藏  所属分类: 
Java