专题五:Spring + Hibernate 编程实例
本实例中,使用Spring作为WEB层和业务逻辑层的容器,Hibernate作为持久层,另外使用JAX-WS2.1做WebService部件,为瘦客户端提供服务。其结构如下图所示:
从这个总图可以看出,这个框架需要完成下列任务:
l 编写实体类和实体主键类
l 编写实体类的持久化操作类及其接口
l 编写业务逻辑类及其接口
l 编写Web界面操作类
l 编写WebService类(给瘦客户端)
l 编写相应的配置文件
本实例使用一个简单的地址本管理应用描述相关的内容。本实例设计工作在Tomcat和glassfish服务器上。建议使用glassfish服务器以获得后续的发展,毕竟glassfish是SUN发布的全功能的应用服务器,而非只有Web容器。
1 环境准备
1.1 安装系统软件
l 安装JDK 6.0 和JAVA EE 5;
l 安装netbeans 6.0做开发工具,使用eclipse也可以;
l 安装Tomcat 6.0做应用服务器;
l 安装GlassFish 2.0做应用服务器
l 安装数据库,这里使用 MS SQL Server Express 2005
1.2 准备组件
l Spring 2.06
l Hibernate 3.2
l JAX-WS 2.1
l MS SQL Server JDBC 1.2
l 其他需要的JAR包
1.3 创建应用
l 使用netbeans创建WebApplication,命名为 AddressBook;
l 选择Tomcat做服务器;
l JAVA平台选择 Java EE 5;
l 创建数据库及数据表。
CREATE DATABASE addressbook;
CREATE TABLE db_address
{
email char(20) not null primary key,
name char(20) not null,
password char(20) not null,
workphone char(20) not null,
homephone char(20) not null,
mobile char(20) not null,
};
1.4 添加基础配置
1.4.1 WEB-INF/web.xml
web.xml文件放置到WEB-INF目录;
l 配置Spring ApplicationContext文件;
l 配置Spring ApplicationContext加载器;
l 配置log4j的文件;
l 配置Spring过虑器防止汉字乱码。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<description>地址本管理应用范例</description>
<display-name>AddressBook</display-name>
<context-param>
<description>Spring main config</description>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<description>Spring applicationContext loader</description>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GB18030</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<servlet-name>AddressBookWeb</servlet-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
|
1.4.2 WEB-INF/classes/applicationContext.xml
applicationContext.xml放置到Source目录,编译时被IDE自动放置到WEB-INF/classes目录。(放置到此处JUnit测试才能通过,否则报加载配置文件错)
l 配置数据库连接池(c3p0DataSource);
l 配置Hibernate Session管理器(sessionFactory);
l 配置数据库事务管理器(transactionManager);
l 配置Spring配置式事务代理(txProxyTemplate)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="c3p0DataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass">
<value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
</property>
<property name="jdbcUrl">
<value>
jdbc:sqlserver://localhost:1433;databaseName=AddressBook
</value>
</property>
<property name="user">
<value>sa</value>
</property>
<property name="password">
<value>welcome</value>
</property>
<property name="minPoolSize">
<value>10</value>
</property>
<property name="acquireIncrement">
<value>10</value>
</property>
<property name="maxPoolSize">
<value>200</value>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="c3p0DataSource" />
</property>
<property name="mappingDirectoryLocations">
<list>
<!-- Hibernate Configure file location -->
<value>classpath*:model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.SQLServerDialect
</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.default_schema"></prop>
<prop key="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
<bean id="txProxyTemplate"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="remove*">PROPAGATION_REQUIRED</prop>
<prop key="del*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
|
注意:
(1)以上系统级基础Bean均是单例的;
(2)txProxyTemplate 是一个抽象的定义,全部业务逻辑Bean的定义将继承其定义,从而获得Spring的配置式事务能力;
(3)此处指定Hibernate的配置文件要放置到WEB-INF/classes/model目录下。
1.4.3 WEB-INF/classes/log4j.properties
# TraceLevel: OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
# Appender: ConsoleAppender、FileAppender、DailyRollingAppender、RollingFileAppender、WriterAppender
# Layout: HTMLLayout、PatternLayout、SimpleLayout、TTCCLayout
# ConversionPattern: %m(message)、%p(TraceLevel)、%r(时间片-毫秒数)、%c(类名)、%t(线程号)、%n("n)、%d{DateFmt}(日期)、%l(全信息)
# log4j.appender.A2.File=dglog.txt
#
# log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
# log4j.appender.A2.file=dglog
# log4j.appender.A2.DatePattern='.'yyyy-MM-dd
# log4j.appender.A2.layout=org.apache.log4j.PatternLayout
# log4j.appender.A2.layout.ConversionPattern= %5r %-5p %c{2} - %m%n
#
# log4j.appender.R=org.apache.log4j.RollingFileAppender
# log4j.appender.R.File= dglog.log
# log4j.appender.R.MaxFileSize=100KB
# log4j.appender.R.MaxBackupIndex=30
# log4j.appender.R.layout=org.apache.log4j.PatternLayout
# log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
#
log4j.rootLogger=ERROR,A1,A2
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%d{yyyy-MM-dd HH":mm":ss}] [%c %M] %-5p %c(line":%L) %x-%m%n
#
log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.File=addressbook.log
log4j.appender.A2.MaxFileSize=1024KB
log4j.appender.A2.MaxBackupIndex=7
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=[%-5p] [%d{yyyy-MM-dd HH":mm":ss}] [%c %M] %c(line":%L) %x-%m%n
|
1.5 测试
发布应用到服务器,检查服务器的日志信息,确认数据库连接是否成功,是否缺少JAR包,日志输出是否正常。
2 创建实体类及主键类
由于派生或替换实体类及主键类的可能性不大,因此对实体类及主键类不必定义接口。每个业务数据对象均需要设计实体类及主键类。此外,业务逻辑层与WEB层交换的model对象,如果无法使用数据库实体,则也应为其定义一个实体类(例如进行多表数据编辑。――多表数据输出则不必定义实体,只需要将多个实体对象放置到模型中即可)。
应注意的是,尽管实体的主键可能是单键,可以使用简单数据类型,但考虑到编码的一致性,建议统一使用主键类作为各实体的主键。
实体类与主键类通常放置到model包中,可以根据实体的数量和性质划分更细的包。
实体类及主键类是普通的JavaBean,只包含数据及其构造方法、Setter和Getter方法,且实现Serializable接口。实体类及主键类必须定义无参构造方法、equals方法、hashCode方法,主键类应定义一个带参构造方法。实体类及实体主键类必须对全部属性进行初始化,确保各属性的值合法,这可以避免很多意外的错误。
2.1 model.key.Address.AddressId
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package model.key.Address;
import java.io.Serializable;
/**
*
* @author
*/
public class AddressId implements Serializable
{
private String id = "";
public AddressId()
{
}
public AddressId(String id)
{
this.id = id.trim();
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final AddressId other = (AddressId) obj;
if (this.id == null || !this.id.equals(other.id))
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 5;
hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
return hash;
}
public String getId()
{
return id.trim();
}
public void setId(String id)
{
this.id = id.trim();
}
}
|
2.2 model.entity.Address.Address
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package model.entity.Address;
import java.io.Serializable;
import model.key.Address.AddressId;
/**
*
* @author
*/
public class Address implements Serializable
{
private AddressId id = new AddressId("");
private String name = "";
private String password = "";
private String workphone = "";
private String homephone = "";
private String mobile = "";
public Address()
{
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Address other = (Address) obj;
if (this.id != other.id && (this.id == null || !this.id.equals(other.id)))
{
return false;
}
return true;
}
@Override
public int hashCode()
{
int hash = 3;
hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
return hash;
}
public AddressId getid()
{
return id;
}
public void setid(AddressId id)
{
this.id = id;
}
public String getHomephone()
{
return homephone.trim();
}
public void setHomephone(String homephone)
{
this.homephone = homephone.trim();
}
public String getMobile()
{
return mobile.trim();
}
public void setMobile(String mobile)
{
this.mobile = mobile.trim();
}
public String getName()
{
return name.trim();
}
public void setName(String name)
{
this.name = name.trim();
}
public String getPassword()
{
return password.trim();
}
public void setPassword(String password)
{
this.password = password.trim();
}
public String getWorkphone()
{
return workphone.trim();
}
public void setWorkphone(String workphone)
{
this.workphone = workphone.trim();
}
}
|
注意:各属性值的trim()可以避免变长字段很多意外情况的发生。
2.3 model/Address.hbm.xml
实体类的Hibernate映射文件放置到model目录下(见1.4.2节的配置)。每个实体类一个文件。文件命令规则为实体类名.hbm.xml。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="model.entity.Address.Address" table="db_address"
dynamic-insert="true" dynamic-update="true" lazy="true">
<composite-id name="id" class="model.key.Address.AddressId">
<key-property name="id" type="string">
<column name="email" length="20" />
</key-property>
</composite-id>
<property name="name" type="string">
<column name="name" length="20" not-null="true" />
</property>
<property name="workphone" type="string">
<column name="workphone" length="20" not-null="true" />
</property>
<property name="homephone" type="string">
<column name="homephone" length="20" not-null="true" />
</property>
<property name="mobile" type="string">
<column name="mobile" length="20" not-null="true" />
</property>
<property name="password" type="string">
<column name="password" length="20" not-null="true" />
</property>
</class>
</hibernate-mapping>
|
2.4 WEB-INF/classes/applicationContext.xml
实体类通过其属性存储持久化数据,因此应是多例的。
<bean id="Address" class="model.entity.Address.Address" scope="prototype">
<property name="name" value= "name"/>
</bean>
<bean id="AddressId" class="model.key.Address.AddressId" scope="prototype">
<property name="id" value= "email"/>
</bean>
注意:增加属性值的目的是为JUnit测试。
|
2.5 单元测试
Spring的spring-mock.jar提供了脱离Web容器的环境下测试Spring框架的功能,因此需将spring-mock.jar包加入测试包,并创建Spring测试类继承AbstractTransactionalSpringContextTests类,覆盖getConfigLocations()方法和runTest()方法。AbstractTransactionalSpringContextTests类提供了加载Spring环境的能力和数据库事务管理能力,它会在测试结束时自动回滚测试中的数据库事务,确保不会对数据形成影响。如果测试中需要提交数据库事务,可以调用setComplete()或setDefaultRollback(boolean defaultRollback)。
2.5.1 Case.SpringTest
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package Case;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.test.AbstractTransactionalSpringContextTests;
import static org.junit.Assert.*;
/**
*
* @author
*/
public class SpringTest extends AbstractTransactionalSpringContextTests
{
@Override
protected String[] getConfigLocations()
{
return new String [] {"classpath*:applicationContext.xml"};
}
public void TestEntity()
{
System.out.println("TestEntity() start ..");
System.out.println("AddressId ..");
AddressId id = (AddressId) applicationContext.getBean("AddressId");
assertNotNull(id);
assertEquals(id.getId(), "email");
System.out.println("Address ..");
Address address = (Address) applicationContext.getBean("Address");
assertNotNull(address);
assertEquals(address.getName(), "name");
System.out.println("TestEntiry() end.");
}
}
|
2.5.2 Test.JUnitTest
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package Test;
import Case.SpringTest;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author
*/
public class JUnitTest
{
@Test
public void main() throws Throwable
{
System.out.println("Start Test ...");
SpringTest t = new SpringTest();
t.setName("TestEntiry");
t.runBare();
}
}
|
3 编写实体类的持久化操作类及其接口
为简化实体类的持久化操作类编写,我们创建持久化基础类完成大部分的持久化任务。各实体类的持久化类均继承持久化基础类。本例中的持久化任务是通过Hibernate完成的,因此持久化基础类以Spring提供的事务和HibernateDaoSupport作为基础的组件。网络上有比较成熟的持久化基础类,可以下载来使用。
为支持各个实体类,持久化基础类使用了JDK5.0增加的泛性类。
持久化各类均放置在dao包中。
3.1 dao.GenericDao
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dao;
import java.io.Serializable;
import java.util.List;
/**
*
* @author
*/
public interface GenericDao<T extends Serializable, PK extends Serializable>
{
public T Select(PK id);
public List<T> SelectAll();
public void Insert(T entity);
public void Update(T entity);
public void Delete(PK id);
}
|
3.2 dao.GenericHibernateDao
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dao;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/**
*
* @author
*/
public class GenericHibernateDao<T extends Serializable, PK extends Serializable>
extends HibernateDaoSupport implements GenericDao<T,PK>
{
private Class<T> entityClass;
public GenericHibernateDao()
{
this.entityClass = null;
Class c = getClass();
Type t = c.getGenericSuperclass();
if(t instanceof ParameterizedType)
{
Type[] p = ((ParameterizedType) t).getActualTypeArguments();
this.entityClass = (Class) p[0];
}
}
public void Delete(PK id)
{
getHibernateTemplate().delete(this.Select(id));
}
public void Insert(T entity)
{
getHibernateTemplate().save(entity);
}
public void Update(T entity)
{
getHibernateTemplate().update(entity);
}
public T Select(Serializable id)
{
return (T) getHibernateTemplate().load(entityClass, id);
}
public List<T> SelectAll()
{
List<T> list;
list = getHibernateTemplate().loadAll(entityClass);
return list;
}
}
|
3.3 dao.AddressDao
实体持久化类接口继承持久化基础类接口,将泛性转换为确定的实体类型。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dao;
import model.entity.Address.Address;
import model.key.Address.AddressId;
/**
*
* @author
*/
public interface AddressDao extends GenericDao<Address, AddressId>
{
}
|
3.4 dao.AddressDaoImp
实体持久化类继承持久化基础类,实现实体持久化类接口。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package dao;
import model.entity.Address.Address;
import model.key.Address.AddressId;
/**
*
* @author
*/
public class AddressDaoImp extends GenericHibernateDao<Address, AddressId> implements AddressDao
{
}
|
3.5 WEB-INF/classes/applicationContext.xml
实体持久化类需在Spring的上下文配置中定义,以便业务逻辑类可以从Spring环境中获取并使用。由于实体持久化类没有存储任何状态数据,因此应使用单例模式。
<bean id="AddressDao" class="dao.AddressDaoImp">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
|
3.6 单元测试
持久化类的测试在实体类测试的基础上进行,需在Spring测试类中增加持久化测试的相关内容。注意如果Dao类还没有纳入事务管理而hibernate配置实体是延迟加载的,则执行select时会出现could not initialize proxy - the owning Session was closed错误,此时可以临时关闭实体类的延时加载选项或将Dao类纳入事务管理(见2.5.1)。
3.6.1 Case.SpringTest
public void TestDao()
{
System.out.println("TestDao() start ..");
System.out.println("测试AddressDao配置 ..");
AddressDao dao = (AddressDao) applicationContext.getBean("AddressDao");
assertNotNull(dao);
System.out.println("测试AddressDao.insert() ..");
AddressId id = (AddressId) applicationContext.getBean("AddressId");
id.setId("_email");
Address address = (Address) applicationContext.getBean("Address");
address.setid(id);
address.setName("_name");
address.setHomephone("_homephone");
address.setWorkphone("_workphone");
address.setMobile("_mobile");
address.setPassword("_password");
dao.Insert(address);
System.out.println("测试AddressDao.select() ..");
address = dao.Select(id);
assertNotNull(address);
assertEquals(address.getid().getId(), "_email");
assertEquals(address.getName(), "_name");
assertEquals(address.getHomephone(), "_homephone");
assertEquals(address.getWorkphone(), "_workphone");
assertEquals(address.getMobile(), "_mobile");
assertEquals(address.getPassword(), "_password");
System.out.println("测试AddressDao.update() ..");
address.setName("_newname");
dao.Update(address);
Address addressnew = dao.Select(id);
assertNotNull(addressnew);
assertEquals(address.getName(), addressnew.getName());
System.out.println("测试AddressDao.delete() ..");
dao.Delete(id);
System.out.println("TestDao() end.");
}
|
注意:测试时Spring会自动执行回滚操作取消对数据库的更改。但如果需要在数据库存储测试的数据,结束测试前可以调用setComplete()提交数据库事务。
3.6.2 Test.JUnitTest
@Test
public void main() throws Throwable
{
System.out.println("Start Test ...");
SpringTest t = new SpringTest();
t.setName("TestDao");
t.runBare();
}
|
4 编写业务逻辑类及其接口
业务逻辑类从界面获取数据,进行业务处理后存储到数据库;或接受界面的请求,从数据库获取数据提交给界面操作。业务逻辑和实体类、Dao类均存在交互。由于实体类是多例的,Dao类是单例的,为减少系统的开销,尽量将业务逻辑类设计为单例的,因此业务逻辑类可以使用Dao类、其他单例类作为属性,但不能使用实体类作为属性。实体类只能在方法中或参数中、Session环境中使用。
为避免重复设计通用化的业务功能部分,建议设计业务逻辑基础类作为其他业务逻辑类的基类,并定义相应的接口。基础类是抽象类,因此不必在Spring中进行配置(因为不会创建其实例)。
业务逻辑类放置到business包及其子包。
4.1 business.GenericService
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package business;
/**
*
* @author
*/
public interface GenericService
{
}
|
4.2 business.GenericServiceImp
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package business;
/**
*
* @author
*/
public class GenericServiceImp implements GenericService
{
}
|
4.3 business.Address.AddressService
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package business.Address;
import business.GenericService;
import java.util.List;
import model.entity.Address.Address;
import model.key.Address.AddressId;
/**
*
* @author
*/
public interface AddressService extends GenericService
{
public void save(Address address);
public Address select(AddressId id);
public List<Address> selectAll();
public void update(Address address);
public void delete(AddressId id);
}
|
4.4 business.Address.AddressServiceImp
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package business.Address;
import business.GenericServiceImp;
import dao.AddressDao;
import java.util.List;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.hibernate.Hibernate;
/**
*
* @author
*/
public class AddressServiceImp extends GenericServiceImp implements AddressService
{
private AddressDao dao;
public void setDao(AddressDao dao)
{
this.dao = dao;
}
public void save(Address address)
{
dao.Insert(address);
}
public Address select(AddressId id)
{
Address address = dao.Select(id);
Hibernate.initialize(address);
return address;
}
public List<Address> selectAll()
{
List<Address> list = dao.SelectAll();
Hibernate.initialize(list);
return list;
}
public void update(Address address)
{
dao.Update(address);
}
public void delete(AddressId id)
{
dao.Delete(id);
}
}
|
注意:由于实体类配置为延迟加载的(见2.3),即从数据库获取时Hibernate为提高性能,仅返回了实体的代理对象,当应用访问代理对象的数据时,才真正从数据库检索数据。为避免延迟检索数据时出现could not initialize proxy - the owning Session was closed,本例中使用Hibernate. initialize()代理业务逻辑操作,模拟业务逻辑中的使用代理对象数据,以便该实例数据能够被Hibernate从数据库中加载。
4.5 WEB-INF/classes/applicationContext.xml
业务逻辑类我们将实施Spring的配置化事务,因此其配置应扩展txProxyTemplate的配置,同时应注意业务逻辑类的方法命名应匹配txProxyTemplate定义的方法名,否则Spring无法使用AOP机制插入事务处理。
<bean id="AddressServiceTarget" class="business.Address.AddressServiceImp">
<property name="dao">
<ref bean="AddressDao"/>
</property>
</bean>
<bean id="AddressService" parent="txProxyTemplate">
<property name="target">
<ref bean="AddressServiceTarget"/>
</property>
</bean>
|
4.6 单元测试
4.6.1 Case.SpringTest
public void TestService()
{
System.out.println("TestService() start ..");
System.out.println("测试AddressService配置 ..");
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
assertNotNull(svr);
System.out.println("测试AddressService.save() ..");
AddressId id = (AddressId) applicationContext.getBean("AddressId");
id.setId("_email");
Address address = (Address) applicationContext.getBean("Address");
address.setid(id);
address.setName("_name");
address.setHomephone("_homephone");
address.setWorkphone("_workphone");
address.setMobile("_mobile");
address.setPassword("_password");
svr.save(address);
System.out.println("测试AddressService.select() ..");
address = svr.select(id);
assertNotNull(address);
assertEquals(address.getid().getId(), "_email");
assertEquals(address.getName(), "_name");
assertEquals(address.getHomephone(), "_homephone");
assertEquals(address.getWorkphone(), "_workphone");
assertEquals(address.getMobile(), "_mobile");
assertEquals(address.getPassword(), "_password");
System.out.println("测试AddressService.update() ..");
address.setName("_newname");
svr.update(address);
Address addressnew = svr.select(id);
assertNotNull(addressnew);
assertEquals(address.getName(), addressnew.getName());
// this.setComplete();
System.out.println("测试AddressService.delete() ..");
svr.delete(id);
System.out.println("TestService() end.");
}
|
4.6.2 Test.JUnitTest
@Test
public void main() throws Throwable
{
System.out.println("Start Test ...");
SpringTest t = new SpringTest();
t.setName("TestService");
t.runBare();
}
|
5 编写Web界面操作类
本例中使用Spring作为Web界面部分,功能也很简单,仅提供Address实体的CRUD操作功能。Spring提供了一套创建Web MVC的类,本例中M使用实体类的实体,V使用JSP文件,输入型视图的C继承Spring的SimpleFormController,输出型视图的C实现Spring的Controller。
Spring的WEB MVC的关系图如下:
5.1 配置Spring Web MVC
Spring Web MVC需在Web.xml中配置Spring前端控制器Servlet及地址映射,同时增加Web MVC自己的上下文配置文件。
5.1.1 WEB-INF/web.xml
配置web.xml装载Spring WEB的控制器及其上下文配置,定义地址匹配,以及字符集编码转换。
<servlet>
<servlet-name>AddressBookWeb</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-web.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>AddressBookWeb</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GB18030</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<servlet-name>AddressBookWeb</servlet-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
|
5.1.2 WEB-INF/applicationContext-web.xml
本文件配置活动类、视图、模型、校验器等。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/append.do">AppendAction</prop>
<prop key="/select.do">SelectAction</prop>
<prop key="/delete.do">DeleteAction</prop>
<prop key="/update.do">UpdateAction</prop>
<prop key="/doUpdate.do">doUpdateAction</prop>
</props>
</property>
</bean>
<!-- Application custom configure --->
<bean id="AppendAction" class="web.inAction.AppendAction">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>Address</value></property>
<property name="commandClass">
<value>model.entity.Address.Address</value>
</property>
<property name="formView"><value>append</value></property>
<property name="successView"><value>show</value></property>
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
<bean id="SelectAction" class="web.inAction.SelectAction">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>AddressId</value></property>
<property name="commandClass">
<value>model.key.Address.AddressId</value>
</property>
<property name="formView"><value>select</value></property>
<property name="successView"><value>show</value></property>
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
<bean id="UpdateAction" class="web.inAction.UpdateAction">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>AddressId</value></property>
<property name="commandClass">
<value>model.key.Address.AddressId</value>
</property>
<property name="formView"><value>update</value></property>
<property name="successView"><value>doUpdate.do</value></property>
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
<bean id="doUpdateAction" class="web.inAction.doUpdateAction">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>Address</value></property>
<property name="commandClass">
<value>model.entity.Address.Address</value>
</property>
<property name="formView"><value>modify</value></property>
<property name="successView"><value>show</value></property>
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
<bean id="DeleteAction" class="web.inAction.DeleteAction">
<property name="sessionForm"><value>true</value></property>
<property name="commandName"><value>AddressId</value></property>
<property name="commandClass"><value>model.key.Address.AddressId</value></property>
<property name="formView"><value>delete</value></property>
<property name="successView"><value>show</value></property>
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
</beans>
|
注意:此处注册的Action Bean应与后续开发的活动类配套,而且ActionBean仅使用业务逻辑层对象作属性。ActionBean应设计为单例的。由于Web访问的并发性,因此ActionBean应注意使用的model应是Session级别的。如果model中包含了非Session或多例的bean时,操作model的该段代码应实施多线程保护。设计ActionBean时,应避免在model中使用单例对象,除非确有必要。
如果需要在多个ActionBean中共享或交换数据,可以将该数据放置到请求的属性、参数或Session环境中,通过HttpServletRequest的属性getAttribute()或参数getParameter()或arg0.getSession().getAttribute()进行访问。
5.2 编写Web活动类
活动类放置到web包。
5.2.1 web.inAction.inGenericAction
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import org.springframework.web.servlet.mvc.SimpleFormController;
/**
*
* @author
*/
public class inGenericAction extends SimpleFormController
{
}
|
5.2.2 web.outAction.outGenericAction
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.outAction;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
*
* @author
*/
public class outGenericAction implements Controller
{
public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
|
5.2.3 web.inAction.SelectAction
SelectAction根据select.jsp输入的AddressId的email值,从数据库检索数据,存储在show.jsp的模型中,供show.jsp显示。如果没有指定值,则检索全部数据。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import business.Address.AddressService;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.web.servlet.ModelAndView;
/**
*
* @author
*/
public class SelectAction extends inGenericAction
{
private AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
@Override
protected ModelAndView onSubmit(Object arg0)
{
List<Address> list;
AddressId id = (AddressId) arg0;
try
{
if(id.getId().equals(""))
{
list = svr.selectAll();
}
else
{
list = new Vector<Address>();
Address address = svr.select(id);
list.add(address);
}
}
catch(Exception e)
{
list = new Vector<Address>();
}
HashMap model = new HashMap();
model.put("AddressList", list);
return new ModelAndView(getSuccessView(), model);
}
}
|
注意:此处是Action返回视图,而非重新定向,因此model是输出的一部分,供Spring生成输出视图时使用。
5.2.4 web.inAction.AppendAction
AppendAction根据append.jsp视图输入的数据,创建Address对象,调用业务逻辑类将其存储到数据库中,然后从数据库获取全部数据,提交给show.jsp视图显示。
Spring 视图与控制间可以交换一个命令对象,如果需要从界面输入多个对象的值时,可以创建一个界面输入专用的实体类,或者将部分对象的值通过没有绑定的单独变量存储(这些值成为HttpServletRequest的属性或参数),在提交处理中赋值给相应的对象。
.jsp
<td>email:</td>
<td>
<input type="text" name="_PK" value="">
</td>
.java
String id = (String) arg0.getParameter("_PK");
|
本活动类定义的命令对象是Address。但Address类的主键是AddressId类,必须进行初始化,否则生成输入界面时报错。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import business.Address.AddressService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
/**
*
* @author
*/
public class AppendAction extends inGenericAction
{
private AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
@Override
protected Map referenceData(HttpServletRequest arg0, Object arg1, Errors arg2) throws Exception
{
Address address = (Address) arg1;
address.setid(new AddressId(""));
address.setName("");
address.setHomephone("");
address.setWorkphone("");
address.setMobile("");
address.setPassword("");
return null;
}
@Override
protected ModelAndView onSubmit(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, BindException arg3) throws Exception
{
Address address = (Address) arg2;
svr.save(address);
List<Address> list = svr.selectAll();
HashMap model = new HashMap();
model.put("AddressList", list);
return new ModelAndView(getSuccessView(), model);
}
}
|
5.2.5 web.inAction.UpdateAction
UpdateAction分两个步骤,第1步骤从界面获取要修改的email;第2步骤根据email从数据库检索数据供编辑。
本步骤将界面指定的email存储在新视图的模型中,然后重定向到编辑界面。此处使用session属性传递参数,还可以使用HttpServletRequest参数传递。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import business.Address.AddressService;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
/**
*
* @author
*/
public class UpdateAction extends inGenericAction
{
private AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
@Override
protected ModelAndView onSubmit(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, BindException arg3) throws Exception
{
AddressId id = (AddressId) arg2;
HashMap model = new HashMap();
arg0.getSession().setAttribute("AddressId", id);
return new ModelAndView(new RedirectView(getSuccessView()), model);
}
}
|
5.2.6 web.inAction.doUpdateAction
本步骤先从模型中得到email,然后根据email从数据库检索address数据供编辑,然后将编辑的数据存储到数据库。
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import business.Address.AddressService;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import model.key.Address.AddressId;
import model.key.Address.AddressId;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
/**
*
* @author
*/
public class doUpdateAction extends inGenericAction
{
private AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
@Override
protected Map referenceData(HttpServletRequest arg0, Object arg1, Errors arg2) throws Exception
{
Address address = (Address) arg1;
AddressId id = (AddressId) arg0.getSession().getAttribute("AddressId");
Address old = svr.select(id);
address.setid(old.getid());
address.setName(old.getName());
address.setHomephone(old.getHomephone());
address.setWorkphone(old.getWorkphone());
address.setMobile(old.getMobile());
address.setPassword(old.getPassword());
return null;
}
@Override
protected ModelAndView onSubmit(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, BindException arg3) throws Exception
{
Address address = (Address) arg2;
svr.update(address);
List<Address> list = svr.selectAll();
HashMap model = new HashMap();
model.put("AddressList", list);
return new ModelAndView(getSuccessView(), model);
}
}
|
5.2.7 web.inAction.DeleteAction
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package web.inAction;
import business.Address.AddressService;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.web.servlet.ModelAndView;
/**
*
* @author
*/
public class DeleteAction extends inGenericAction
{
private AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
@Override
protected ModelAndView onSubmit(Object arg0)
{
List<Address> list;
AddressId id = (AddressId) arg0;
try
{
if(!id.getId().equals(""))
{
svr.delete(id);
}
list = svr.selectAll();
}
catch(Exception e)
{
list = new Vector<Address>();
}
HashMap model = new HashMap();
model.put("AddressList", list);
return new ModelAndView(getSuccessView(), model);
}
}
|
5.3 编写视图
本例使用JSP作为视图。非Spring管理的视图应放置到WebRoot的相对各目录下。由Spring管理的视图,应放置到WEB-INF/jsp目录下(参见5.1.2配置)。
5.3.1 WebRoot/index.jsp
index.jsp提供执行增加、修改、删除、检索的连接,应放置到WebRoot。
<%@page contentType="text/html;charset=GB18030"%>
<html>
<head>
<title>AddressBook Application</title>
</head>
<body>
<h2 align="center">AddressBook Application</h2>
<hr width="100%" size="2">
<a href="select.do">检索</a>
<a href="append.do">添加</a>
<a href="update.do">修改</a>
<a href="delete.do">删除</a>
</body>
</html>
|
5.3.2 WEB-INF/jsp/select.jsp
select.jsp提供检索条件输入。
<%@page contentType="text/html" pageEncoding="GB18030"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title>Select Address</title></head>
<body><font size="6"><strong>
Which email do you want to select?</strong></font>
<form method="post">
<table width="440" height="27">
<tr>
<td>email:</td>
<td>
<spring:bind path="AddressId.id">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
<input type="submit" alignment="center" value="Select"> </td>
</tr>
</table>
</form>
</body>
</html>
|
path定义视图的模型。
注意:如果实体类没有初始化其各属性,且Action也没有调用referenceData()初始化该对象的各属性,则不能指定绑定路径为AddressId.id,也不能在<input>的name项使用${status.expression}。
5.3.3 WEB-INF/jsp/show.jsp
Show.jsp显示获取的Address数据。其数据均由其他Action类从数据库获取后填写到其模型中,供Show.jsp输出。此视图是所有活动的输出视图,只使用了JTSL标记库。
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head><title>Selected Address List</title></head>
<body>
<h2 align="center">Selected Address List</h2><hr width="100%" size="2">
<table width="733" border="1" height="56">
<tbody>
<tr>
<td><strong>Email</strong></td>
<td><strong>Name</strong></td>
<td><strong>HomePhone</strong></td>
<td><strong>WorkPhone</strong></td>
<td><strong>Mobile</strong></td>
<td><strong>Password</strong></td>
</tr>
<c:forEach items="${AddressList}" var="address">
<tr>
<td><c:out value="${address.id.id}" /> </td>
<td><c:out value="${address.name}" /></td>
<td><c:out value="${address.homephone}" /></td>
<td><c:out value="${address.workphone}" /></td>
<td> <c:out value="${address.mobile}" /></td>
<td><c:out value="${address.password}" /> </td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
|
5.3.4 WEB-INF/jsp/append.jsp
<%@page contentType="text/html;charset=GB18030"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>AddressBook Application</title>
</head>
<body><font size="6">
<strong>Append New Address</strong></font>
<spring:hasBindErrors name="Address">
<br>
<spring:bind path="Address.*">
<font color="red">
<b>${status.errorMessage}</b>
</font><br>
</spring:bind>
<br>
</spring:hasBindErrors>
<form method="post">
<table width="440" height="27">
<tr>
<td>email:</td>
<td>
<spring:bind path="Address.id.id">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>name:</td>
<td>
<spring:bind path="Address.name">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>workphone:</td>
<td>
<spring:bind path="Address.workphone">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>homephone:</td>
<td>
<spring:bind path="Address.homephone">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>mobile:</td>
<td>
<spring:bind path="Address.mobile">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>password:</td>
<td>
<spring:bind path="Address.password">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<input type="submit" alignment="center" value="Append">
</tr>
</table>
</form>
</body>
</html>
|
5.3.5 WEB-INF/jsp/update.jsp
<%@page contentType="text/html" pageEncoding="GB18030"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title>Select Address</title></head>
<body><font size="6"><strong>
Which email do you want to update?</strong></font>
<form method="post">
<table width="440" height="27">
<tr>
<td>email:</td>
<td>
<spring:bind path="AddressId">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
<input type="submit" alignment="center" value="Update"> </td>
</tr>
</table>
</form>
</body>
</html>
|
5.3.6 WEB-INF/jsp/modify.jsp
<%@page contentType="text/html" pageEncoding="GB18030"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title>Modify Address</title></head>
<body><font size="6">
<strong>Modify Address</strong></font>
<spring:hasBindErrors name="Address">
<br>
<spring:bind path="Address.*">
<font color="red">
<b>${status.errorMessage}</b>
</font><br>
</spring:bind>
<br>
</spring:hasBindErrors>
<form method="post">
<table width="440" height="27">
<tr>
<td>email:</td>
<td>
<spring:bind path="Address.id.id">
<input type="text" name="${status.expression}" value="${status.value}" disabled>
</spring:bind>
</td>
</tr>
<tr>
<td>name:</td>
<td>
<spring:bind path="Address.name">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>workphone:</td>
<td>
<spring:bind path="Address.workphone">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>homephone:</td>
<td>
<spring:bind path="Address.homephone">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>mobile:</td>
<td>
<spring:bind path="Address.mobile">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<td>password:</td>
<td>
<spring:bind path="Address.password">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
</td>
</tr>
<tr>
<input type="submit" alignment="center" value="Modify">
</tr>
</table>
</form>
</body>
</html>
|
5.3.7 WEB-INF/jsp/delete.jsp
<%@ page language="java" pageEncoding="GB18030"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html lang="true">
<head><title>Select Address</title></head>
<body><font size="6"><strong>
Which email do you want to delete?</strong></font>
<form method="post">
<table width="440" height="27">
<tr>
<td>email:</td>
<spring:bind path="AddressId.id">
<input type="text" name="${status.expression}" value="${status.value}">
</spring:bind>
<input type="submit" alignment="center" value="Delete">
</tr>
</table>
</form>
</body>
</html>
|
6 编写WebService类(给瘦客户端)
本例使用JAX-WS作为WebService的发布工具。
由于WebService调用业务逻辑类完成业务处理,而业务逻辑类是由Spring管理的,因此必须将JAX-WS与Spring进行集成。(如果在WebService中脱离Spring管理直接创建业务逻辑类对象,由于业务逻辑类缺少Spring赋予的依赖关系,将导致创建的业务逻辑类对象无法工作或无法使用Spring环境的其他特性,如配置式事务)。
将JAX-WS与Spring进行集成,方法一就是在WebService中获取Spring的applicationContext,然后通过applicationContext访问Spring环境的各对象。方法二是使用jaxws-spring.jar工具包。该包使用com.sun.xml.ws.transport.http. servlet.WSSpringServlet替换JAX-WS默认的com.sun.xml.ws.transport.http. servlet .WSServlet,实现在Spring环境下创建WebService对象,从而使得WebService对象可以访问Spring环境的其他对象,包括业务逻辑类对象。jaxws-spring.jar需要xbean-spring.jar作为支持包。
方法一可以在Tomcat和glassfish中使用,但WebService对象本身是脱离Spring管理的,因此不能使用Spring的各种服务,只能通过applicationContext访问Spring环境中的对象。方法二WebService对象本身是受Spring管理的,因此可以使用Spring的各种服务,但在glassfish不支持此模式。本例中使用方法一即可满足要求。
6.1 方法一
6.1.1 Tomcat
使用netbeans创建AddressWebService,如果运行环境是Tomcat,则netbeans创建JAX-WS WebService时,会自动进行下列修改:
6.1.1.1 WEB-INF/web.xml
Web.xml增加JAX-WS的监听器、Servlet及地址映射。
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>AddressWebService</servlet-name>
<servlet-class>
com.sun.xml.ws.transport.http.servlet.WSServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AddressWebService</servlet-name>
<url-pattern>/AddressWebService</url-pattern>
</servlet-mapping>
|
6.1.1.2 WEB-INF/sun-jaxws.xml
JAX-WS使用本文件配置环境中存在的WebService,而该WebService具有的功能(WSDL),则是根据Java 5风格的注释信息自动生成的。
<?xml version="1.0" encoding="UTF-8"?>
<endpoints version="2.0" xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime">
<endpoint implementation="websrv.AddressWebService" name="AddressWebService" url-pattern="/AddressWebService"/>
</endpoints>
|
6.1.1.3 websrv/AddressWebService
本例中我们创建下列服务:
l public Address Select(AddressId id);
l public void Delete(AddressId id);
l public void Append(Address address);
l public void Modify(Address address);
l public List<Address> SelectAll().
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package websrv;
import business.Address.AddressService;
import java.util.List;
import javax.annotation.Resource;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.servlet.ServletContext;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
*
* @author
*/
@WebService()
public class AddressWebService
{
@Resource
private WebServiceContext context;
/**
* Select Address from database.
* @param id
* @return
*/
@WebMethod(operationName = "Select")
public Address Select(@WebParam(name = "id") AddressId id)
{
ServletContext servletContext = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
Address address = svr.select(id);
return address;
}
/**
* Web service operation
* @param address
*/
@WebMethod(operationName = "Update")
public void Update(@WebParam(name = "address") Address address)
{
ServletContext servletContext = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
svr.update(address);
}
/**
* Web service operation
* @param address
*/
@WebMethod(operationName = "Insert")
public void Insert(@WebParam(name = "address") Address address)
{
ServletContext servletContext = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
svr.save(address);
}
/**
* Web service operation
* @param id
*/
@WebMethod(operationName = "Delete")
public void Delete(@WebParam(name = "id") AddressId id)
{
ServletContext servletContext = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
svr.delete(id);
}
/**
* Web service operation
* @return
*/
@WebMethod(operationName = "SelectAll")
public List<Address> SelectAll()
{
ServletContext servletContext = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT);
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
AddressService svr = (AddressService) applicationContext.getBean("AddressService");
return svr.selectAll();
}
}
|
@resource注释指示容器创建WebService对象时自动将指定的资源注入对象中。
6.1.2 glassfish
glassfish自身包含了JAX-WS包,部署在glassfish时不必使用JAX-WS包,而且不必加载JAX-WS的Servlet。调整如下:
(1) 删除JAX-WS包;
(2) 编辑WEB-INF/web.xml取消JAX-WS的监听器、Servlet、地址映射;
<!--
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>AddressWebService</servlet-name>
<servlet-class>
com.sun.xml.ws.transport.http.servlet.WSServlet
</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AddressWebService</servlet-name>
<url-pattern>/AddressWebService</url-pattern>
</servlet-mapping>
-->
|
6.2 方法二
方法二使用jaxws-spring.jar和xbean-spring.jar,将创建WebService对象任务委托给Spring,因此可以使用Spring的各种服务,但WebService的发布模式与glassfish冲突,因此无法在glassfish中使用。
6.2.1 WEB-INF/web.xml
使用com.sun.xml.ws.transport.http. servlet.WSSpringServlet替换JAX-WS默认的com.sun.xml.ws.transport.http. servlet .WSServlet。
<servlet>
<servlet-name>AddressWebService</servlet-name>
<servlet-class>
com.sun.xml.ws.transport.http.servlet.WSSpringServlet
</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AddressWebService</servlet-name>
<url-pattern>/AddressWebServiceService</url-pattern>
</servlet-mapping>
|
注意:glassfish自动生成的服务名为AddressWebServiceService,为避免更换应用服务器导致客户端修改,因此此处修改Tomcat下的服务名保持与glassfish一致。此外,还需修改WEB-INF/sun-jaxws.xml与之配套。
<endpoint
implementation="websrv.AddressWebService"
name="AddressWebService"
url-pattern="/AddressWebServiceService"/>
|
6.2.2 WEB-INF/classes/applicationContext.xml
本文件注册WebService对象为bean,并由Spring注入业务逻辑层对象。
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:ws="http://jax-ws.dev.java.net/spring/core"
xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
http://jax-ws.dev.java.net/spring/core
http://jax-ws.dev.java.net/spring/core.xsd
http://jax-ws.dev.java.net/spring/servlet
http://jax-ws.dev.java.net/spring/servlet.xsd">
......
<wss:binding url="/AddressWebServiceService">
<wss:service>
<ws:service bean="#AddressWebService" />
</wss:service>
</wss:binding>
<bean id="AddressWebService" class="websrv.AddressWebService" >
<property name="svr">
<ref bean="AddressService" />
</property>
</bean>
|
6.2.2.1 websrv/AddressWebService
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package websrv;
import business.Address.AddressService;
import java.util.List;
import javax.annotation.Resource;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.servlet.ServletContext;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;
import model.entity.Address.Address;
import model.key.Address.AddressId;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
*
* @author
*/
@WebService()
public class AddressWebService
{
AddressService svr;
public void setSvr(AddressService svr)
{
this.svr = svr;
}
/**
* Select Address from database.
* @param id
* @return
*/
@WebMethod(operationName = "Select")
public Address Select(@WebParam(name = "id") AddressId id)
{
return svr.select(id);
}
/**
* Web service operation
* @param address
*/
@WebMethod(operationName = "Update")
public void Update(@WebParam(name = "address") Address address)
{
svr.update(address);
}
/**
* Web service operation
* @param address
*/
@WebMethod(operationName = "Insert")
public void Insert(@WebParam(name = "address") Address address)
{
svr.save(address);
}
/**
* Web service operation
* @param id
*/
@WebMethod(operationName = "Delete")
public void Delete(@WebParam(name = "id") AddressId id)
{
svr.delete(id);
}
/**
* Web service operation
* @return
*/
@WebMethod(operationName = "SelectAll")
public List<Address> SelectAll()
{
return svr.selectAll();
}
}
|
7 异常处理
Java包含两大类异常,即RuntimeException和Checked Exception异常。
Checked Exception是程序必须进行处理的异常。如果某方法调用抛出Checked Exception异常的方法,则此方法要么捕获该异常,要么声明自身可能抛出该异常。而RuntimeException异常则方法可以不必捕获或继续声明。换句话说,如果应用必须对某异常进行特殊的处理,则应将该异常定义为Checked Exception;而应用只需知道发生了异常,并不关心该异常是什么异常时,则应将该异常定义为RuntimeException。因此,业务需求规定的异常情况应定义为Checked Exception。
Spring的配置式事务环境规定,仅在Spring捕获到RuntimeException异常时才会触发事务回滚。通常情况下发生系统级或业务级异常时,均应回滚事务,显然Spring默认的模式不满足要求,需要进行调整。调整的做法是修改applicationContext.xml配置,指明当发生某Checked Exception异常时,Spring也应做回滚处理。(参见org.springframework. transaction.interceptor. TransactionAttribute)
<bean id="txProxyTemplate"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="remove*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="del*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
|
更改配置后,所有的异常均会导致Spring执行事务回滚处理。如果方法中希望发生某异常时能提交事务,则应在方法中捕获该异常,将其转换为非配置列表的其他异常,从而触发Spring提交事务(目前应没有这样的特殊要求)。
复杂的业务逻辑通常会有较多的业务级异常,这些异常的处理模式也基本相同,而且也需要将这些异常通知界面,以便操作员清楚发生了什么异常。如果完全使用返回码,则程序处处都需要对返回码进行处理,影响程序的质量和可靠性,因此应尽量使用抛出异常模式。
业务级异常有两种可选的方案:一是仅使用一种异常类,由异常对象的错误码判断发生了什么异常;二是为每种异常设计一个异常类,从而形成一个异常体系。前者适合需要特殊处理的异常较多的情况,后者适合个别异常需要特殊处理的情况。由于业务需求的不确定性,系统设计时很难确定需进行特别处理的异常,因此建议使用单异常类多错误码的模式,这样也可以简化程序的设计,仅需要捕获一种异常即可。
7.1 通用异常类
7.2 业务逻辑层异常处理
7.3 WEB层异常处理
7.4 WebService层异常处理
MVC
如果想在另一个页面中显示错误,则可以:
@Override
protected ModelAndView onSubmit(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, BindException arg3) throws Exception
{
arg3.reject("AddressId", "…error message…"); // 创建错误对象
Map model = arg3.getModel();
model.put("AddressId", arg2); // 添加Command对象到模型
model.put("errors", arg3); // 添加错误到模型
return new ModelAndView(getFormView(), model);
}
|
如果想在同一个页面中显示错误,则可以:
@Override
protected ModelAndView onSubmit(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, BindException arg3) throws Exception
{
arg3.reject("AddressId.id", "没有此数据!");
return showForm(arg0, arg1, arg3);
}
|
JSP:
<spring:hasBindErrors name="AddressId">
<br>
<spring:bind path="AddressId">
<font color="red">
<b>${status.errorMessage}</b>
</font><br>
</spring:bind>
<br>
</spring:hasBindErrors>
|
错误页:
8 总结
l 如果某个类可能变更实现方法,则应为其设计接口,以便调用者可以不必修改即可使用新的实现。因此Dao类与业务逻辑类应设计接口,而实体类、主键类、活动类不必设计接口。
l 带状态的bean应配置为多例(prototype),而无状态的bean应配置为单例(single),Web MVC环境下需在多个视图间共享的bean应配置为session,单个视图中使用的bean应配置为request。
l session和request类型的bean应定义代理类;
l 实体类和主键类是多例的,其他非WEB MVC的类是单例的;
l 为每个数据库对象建立一个实体类和主键类;
l 实体类与主键类必须初始化所有的域;字符串域应trim()。
l 主键类必须提供一个带参的构造函数初始化主键;
l 继承框架类时应增加一个基础类隔离框架类与应用类;
l 实体类、主键类、Dao类不必处理异常;业务逻辑类应截获并处理所有的异常,并将全部异常包括(系统级的异常)转换为应用级异常然后再抛出,以避免影响Spring的配置式事务;Web应截获并处理全部异常,根据异常类型决定显示在当前页做提示,还是显示给专门的错误页面。WebService应将异常转换为返回码、错误码与错误信息,传递给客户端。
posted on 2007-11-20 11:55
飞鹰 阅读(4957)
评论(1) 编辑 收藏