How to organize SSH2 project
深入解析SSH2项目结构的细节与实现
这篇文章将深入拆解SSH2项目。说明SSH2系统搭建过程中使用到的相关技术以及谁是整个结构的核心。用于解释的示例程序是:《轻量级JavaEE企业应用实战》书中第十章的《简单工作流系统》。这个程序的原始版本是有错误的,并且是Eclipse平台下的;我将这个程序迁移到IntelliJ IDEA 12下来,依托原有的类和Jsp页面,进行了少量的修改。创建了一个Maven Web应用工程。利用maven来管理整个项目的生命周期和Jar包依赖。这篇文章可能会很长。如何使用这个新的开发环境:IntelliJ IDEA,如果您遇到困难可以给我留言,我会及时帮您解答。当你去一个公司面试的时候,你多熟悉几个IDE对你只有好处,没有坏处,何况会Eclipse的人满大街都是了。
以这个项目例子为工具,对如何组织一个Java EE应用系统进行细节步骤上的总结,(注意!并不是为了教您如何使用SSH2)以方便未来的工作。同时将他公布出来,也算为开源做一点微薄的贡献吧。文章的书写顺序就是搭建一个JavaEE应用系统的一般步骤。文章中一些精彩的总结点取自网络。至于为什么要选择这本书中的这个例子,原因是1. 你在公司的项目是客户的财产,你不能拿来随便用,要付法律责任的。2. 《轻量级JavaEE企业应用实战》这本书还是很牛逼的。在这个例子的基础上力求深加工,为以后积累经验。
下面介绍使用到的工具:
IDEA安装程序 :http://yunpan.cn/Q5txXyEKXVLs3 访问密码 0e26(此链接永久开放)
IDEA破解程序 :http://yunpan.cn/QeV2jC48Mi6WA 访问密码 cf26
MySql数据库 :http://yunpan.cn/Q5tYXUUyYTJCT 访问密码 a61d
Tomcat7.* :http://yunpan.cn/Q5tYUvWdNhjJh 访问密码 779e
Navicate10 :http://yunpan.cn/Q5tYpU9CEBc2L 访问密码 5dbd
apache-maven-3.0.4 :http://yunpan.cn/Q5tqLmDnpQwRT 访问密码 5ea3
(3.0.4及以上版本;此文章不介绍Maven是什么,以及该如何配置Maven等基础问题。)
项目界面截图
进入正题:How to organize a SSH2 project?
1 需求
首先你需要了解这个项目的需求、系统功能、业务操作、以及系统的相关技术实现等。划分出系统的结构和功能模块,设计出数据库。这一部分是项目前期的需求调研部分做的事,不做重点介绍。
2 Hibernate持久层设计
这是开发设计中重要的第二步。第一步是设计数据库的主外键和关联关系。
图-4 domain对象
Hibernate的开发流程一般有如下几个步骤:
1、编写domain对象:持久化类。
2、加入hibernate.jar和其依赖的包。(Maven工程由Pom.xml文件中定义)
3、编写XX.hbm.xml映射文件。(也可以用annotation的方式替代*.hbm.xml文件)
4、编写hibernate.cfg.xml配置文件。必须要提供以下几个参数:connection.driver_class、 connection.url、connection.username、connection.password、dialect、hbm2ddl.auto。
5、编写HibernateUtil工具类、主要用于完成hibernate的初始化过程和提供一个获得session的方 法(可选)。
6、编写实现类。
Domain对象如何编写可以随便看看一个类就好。对于hibernate.cfg.xml这个文件并不是所有的项目都一定还会有这个文件。尤其是现在hibernate.cfg.xml几乎不会出现在项目的资源文件夹下了。Spring包含了定义Hibernate的各种属性的信息。如:定义数据源Bean、设置连接数据库的驱动、URL、用户名、密码连接池最大连接数、最小连接数、初始连接数等参数、依赖注入数据源、列出Hibernate映射文件、SessionFactory属性等等。这些配置信息都是开发中的小细节部分,他们代表着一个庞大项目的重要的、不可缺少的步骤;这些就是一个SSH2项目的细节。
从上面这段话中我们可以看到一个问题:谁是SSH2的核心?Struts?Hibernate?还是Spring?SSH2轻量级架构兴起的时间并不久,在最初开始使用SSH的时候,hibernate.cfg.xml文件还是必要的,但是时至今日,这个文件已经不重要了,并不出现在资源文件夹下了;而表现层的东西并不止Struts一种。这证明了什么?Spring已经成为了整个SSH2项目结构的核心内容。Spring的核心配置文件:applicationContext.xml文件成为了整个资源文件的核心配置文件。现在Spring推出了自己的MVC框架:SpringMVC。整体来讲发展前景要比SSH2更好。
Hibernate体系结构的概要图:
从这个图可以看出,Hibernate使用数据库和配置信息来为应用程序提供持久化服务(以及持久的对象)。Hibernate.propertites和XML Mapping 就是hibernate.cfg.xml文件或XX.hbm.xml文件。
将应用层从底层的JDBC/JTA API中抽象出来,而让Hibernate来处理这些细节
3 编写持久化类:
虽然Hibernate对持久化类没有太多的要求,但是我们应该遵循如下规则:
1、提供一个无参的构造函数。
2、提供一个标识属性。
3、为持久化类的每个属性实现getter和setter方法
4、使用非final类
持久化对象有自己的普通属性,这些属性通常对应着数据库的字段。Hibernate对于持久化对象并没有太多的要求,只要求持久化对象提供无参数构造器,如果需要将这些对象放入HashSet集合中还应该重写hashCode()和equals()两个方法。
package com.hongbo.attsystem.domain; import java.io.Serializable; import java.util.*; /** * Created with IntelliJ IDEA. * User: Yangcl * Date: 13-5-15 * Time: 下午3:39 * To change this template use File | Settings | File Templates. */ public class Employee implements Serializable { private static final long serialVersionUID = 48L; //标识属性 private Integer id; //员工姓名 private String name; //员工密码 private String pass; //员工工资 private double salary; //员工对应的经理 private Manager manager; //员工对应的出勤记录 private Set<Attend> attends = new HashSet<Attend>(); //员工对应的工资支付记录 private Set<Payment> payments = new HashSet<Payment>(); //无参数的构造器 public Employee() { } //初始化全部属性的构造器 public Employee(Integer id , String name , String pass , double salary , Manager manager , Set<Attend> attends , Set<Payment> payments) { this.id = id; this.name = name; this.pass = pass; this.salary = salary; this.manager = manager; this.attends = attends; this.payments = payments; } //id属性的setter和getter方法 public void setId(Integer id) { this.id = id; } public Integer getId() { return this.id; } //name属性的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } //pass属性的setter和getter方法 public void setPass(String pass) { this.pass = pass; } public String getPass() { return this.pass; } //salary属性的setter和getter方法 public void setSalary(double salary) { this.salary = salary; } public double getSalary() { return this.salary; } //manager属性的setter和getter方法 public void setManager(Manager manager) { this.manager = manager; } public Manager getManager() { return this.manager; } //attends属性的setter和getter方法 public void setAttends(Set<Attend> attends) { this.attends = attends; } public Set<Attend> getAttends() { return this.attends; } //payments属性的setter和getter方法 public void setPayments(Set<Payment> payments) { this.payments = payments; } public Set<Payment> getPayments() { return this.payments; } //重写equals()方法,只要name、pass相同的员工即认为相等。 public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == Employee.class) { Employee employee = (Employee)obj; return this.getName().equals(employee.getName()) && this.getPass().equals(employee.getPass()); } return false; } //根据员工的name、pass来计算hashCode值 public int hashCode() { return name.hashCode() + pass.hashCode() * 17; } }
持久化对象有如下几个对象状态:
1、瞬态:对象有new操作符创建,且尚未与Hibernate Session关联的对象被认为处于瞬态。
2、持久化:持久化实例在数据库中有对应的记录,并且拥有一个持久化标识
3、托管:某个实例曾经处于持久化状态,但随着与之关联的Session被关闭了额,那么该对象就变成了托管 状态。
三种状态的演变图如下:Hibernate提供save()、persist()方法可以将对象转变为持久化状态。但是两个方法存在一点区别:save()方法保存持久化对象时,该方法返回持久化对象的标识属性值,而且他会立即将持久化对象的数据插入到数据库中。但是persist()方法就没有任何返回值,它保证当他在一个事务外部被调用时,并不会立即转换成insert语句,这个特性对于我们在封装一个长回话流程的时候,显得很重要。
同时我们还可以通过load()和get()方法来加载一个持久化对象。两者都是根据主键装载持久化实例的。但是两者的区别就在于:get()方法会立即加载,而load()方法会延迟加载。
4 映射持久化类
映射持久化类通过*.hbm.xml文件去操作,也可以通过annotation的方式。这里选择使用配置文件的方式去做。以Employee.hbm.xml文件为例,给出一个描述配置文件所需的步骤。
1. 指定Hibernate映射文件的DTD信息
2. Hibernate映射文件的根元素<hibernate-mapping>
3. 使用只读缓存<cache usage="read-only"/>
4. 映射标识属性<idname="id" type="integer" column="emp_id">
5. 映射标识属性 –>指定使用identity主键生成策略:<generator class="identity"/>
6. 映射和领域对象(Domain Object)层的关联关系(见Tips1: JavaEE分层模型)
<many-to-one name="manager" column="mgr_id" class="Manager" lazy="false"/>
7. 映射父类和子类的关联关系(属于映射领域对象层的一种情况,见下图)
<subclass…/>继承策略
Tips1: JavaEE分层模型
1. Domain Object(领域对象)层:由POJO(Plain old java objcet,普通的、传统的Java对象)组成
包含各自所要的业务逻辑方法。
2. DAO(Data access object,数据访问对象)层:这一层由DAO组件组成,这些DAO实现了对数据库的
创建、查询、更新和删除。
3. 业务逻辑层:包含各种业务逻辑对象、方法。这些业务逻辑方法仅仅用于暴露领域对象层所实现
的业务逻辑方法,也可能是依赖DAO组件实现的业务逻辑方法。
4. 控制器层:包含了一系列的控制器。这些控制器用于拦截用户请求,并调用业务逻辑组件的业务
逻辑方法,处理用户请求,并根据处理结果转发到不同的表现层组件。
5. 表现层:由JSP页面、Ext等组成,负责收集用户请求,显示处理结果。
Tips2: 关于继承策略的选择
<subclass…/>继承策略。使用这种映射策略会把整棵继承树的所有实例都保存在一张数据表内,因此子类增加的数据列都不能有非空约束。实际需要添加非空约束也不行。这种映射策略的性能最好,无论应用程序是需要查询子类实体,还是进行多态查询,都只需要在一个表中进行查询即可。
Employee.hbm.xml完整配置代码
<?xml version="1.0" encoding="GBK"?> <!-- 指定Hibernate映射文件的DTD信息 --> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- Hibernate映射文件的根元素 --> <hibernate-mapping package="com.hongbo.attsystem.domain"> <class name="Employee" table="emp_table" discriminator-value="1"> <!-- 使用只读缓存 --> <cache usage="read-only"/> <!-- 映射标识属性 --> <id name="id" type="integer" column="emp_id"> <!-- 指定使用identity主键生成策略 --> <generator class="identity"/> </id> <discriminator column="emp_type" type="int"/> <!-- 映射普通属性 --> <property name="name" column="emp_name" type="string" not-null="true" length="50" unique="true"/> <property name="pass" column="emp_pass" type="string" not-null="true" length="50"/> <property name="salary" column="emp_salary" type="double" not-null="true" /> <!-- 映射和Manager的关联关系 --> <many-to-one name="manager" column="mgr_id" class="Manager" lazy="false"/> <!-- 映射和Attend之间的关联关系 --> <set name="attends" inverse="true"> <key column="emp_id" /> <one-to-many class="Attend"/> </set> <!-- 映射和Payment之间的关联关系 --> <set name="payments" inverse="true"> <key column="emp_id" /> <one-to-many class="Payment"/> </set> <!-- 映射Employee的子类Manager --> <subclass name="Manager" discriminator-value="2"> <!-- 映射Manager的普通属性 --> <property name="dept" column="dept_name" type="string" length="50"/> <!-- 映射和Employee之间的关联关系 --> <set name="employees" inverse="true"> <key column="mgr_id" /> <one-to-many class="Employee"/> </set> <!-- 映射和CheckBack之间的关联关系 --> <set name="checks" inverse="true"> <key column="mgr_id" /> <one-to-many class="CheckBack"/> </set> </subclass> </class> </hibernate-mapping>
5 实现dao层 –> dao组件定义
DAO(Data access object,数据访问对象)层。引入DAO模式后,每个DAO组件包含了数据库
的访问逻辑;每个DAO组件可以对一个数据库表完成基本的CRUD(增删改查)操作。DAO模式实现
至少需要如下三个部分:
1. DAO工厂类
2. DAO接口
3. DAO接口实现类
使用DAO接口的原因是:避免业务逻辑组件与特定的DAO组件耦合。如下几个方法是通用的。
1. get(Serializable id):根据主键加载持久化实例
2. save(Object entity):保存持久化实例
3. update(Object entity):更新持久化实例
4. delete(Object entity):删除持久化实例
5. delete(Serializable id):根据主键来删除持久化实例。
6. findAll():获取数据表中全部的持久化实例。
在这一步,你需要创建dao组件的接口类,你在这里需要认真看的是:方法前用的类型。为什么有的用list,有的用其他什么。下面给出EmployeeDao的源代码:
package com.hongbo.attsystem.dao; import com.hongbo.attsystem.domain.Employee; import com.hongbo.attsystem.domain.Manager; import java.util.List; /** * Created with IntelliJ IDEA. * User: Yangcl * Date: 13-5-15 * Time: 下午4:46 * To change this template use File | Settings | File Templates. */ public interface EmployeeDao { /** * 根据标识属性来加载Employee实例 * @param id 需要加载的Employee实例的标识属性值 * @return 指定标识属性对应的Employee实例 */ Employee get(Integer id); /** * 持久化指定的Employee实例 * @param employee 需要被持久化的Employee实例 * @return Employee实例被持久化后的标识属性值 */ Integer save(Employee employee); /** * 修改指定的Employee实例 * @param employee 需要被修改的Employee实例 */ void update(Employee employee); /** * 删除指定的Employee实例 * @param employee 需要被删除的Employee实例 */ void delete(Employee employee); /** * 根据标识属性删除Employee实例 * @param id 需要被删除的Employee实例的标识属性值 */ void delete(Integer id); /** * 查询全部的Employee实例 * @return 数据库中全部的Employee实例 */ List<Employee> findAll(); /** * 根据用户名和密码查询员工 * @param emp 包含指定用户名、密码的员工 * @return 符合指定用户名和密码的员工集合 */ List<Employee> findByNameAndPass(Employee emp); /** * 根据用户名查询员工 * @param name 员工的用户名 * @return 符合用户名的员工 */ Employee findByName(String name); /** * 根据经理查询员工 * @param mgr 经理 * @return 该经理对应的所有员工 */ List<Employee> findByMgr(Manager mgr); }
6 实现dao层 –>实现dao组件
Spring为hibernate提供的Dao基类是:HibernateDaoSupport,该类只需要传入一个SessionFactory引用即可得到一个HibernateTemplate实例,HibernateTemplate的功能非常强大,很容以实现数据库的大部分操作。示例程序扩展了HibernateDaoSupport,提供了一个
package com.hongbo.support; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.hibernate.SessionFactory; import org.hibernate.Session; import org.hibernate.Query; import org.hibernate.HibernateException; import java.sql.SQLException; import java.util.List; /** * */ public class FenYeHibernateDaoSupport extends HibernateDaoSupport { /** * 使用hql语句进行分页查询 * @param hql 需要查询的hql语句 * @param offset 第一条记录索引 * @param pageSize 每页需要显示的记录数 * @return 当前页的所有记录 */ public List findByPage(final String hql,final int offset, final int pageSize) { //通过一个HibernateCallback对象来执行查询 List list = getHibernateTemplate().executeFind(new HibernateCallback() { //实现HibernateCallback接口必须实现的方法 public Object doInHibernate(Session session)throws HibernateException, SQLException { //执行Hibernate分页查询 List result = session.createQuery(hql).setFirstResult(offset).setMaxResults(pageSize).list(); return result; } }); return list; } /** * 使用hql语句进行分页查询 * @param hql 需要查询的hql语句 * @param value 如果hql有一个参数需要传入,value就是传入hql语句的参数 * @param offset 第一条记录索引 * @param pageSize 每页需要显示的记录数 * @return 当前页的所有记录 */ public List findByPage(final String hql , final Object value ,final int offset, final int pageSize) { //通过一个HibernateCallback对象来执行查询 List list = getHibernateTemplate() .executeFind(new HibernateCallback() { //实现HibernateCallback接口必须实现的方法 public Object doInHibernate(Session session)throws HibernateException, SQLException { //执行Hibernate分页查询 , 为hql语句传入参数 List result = session.createQuery(hql).setParameter(0, value).setFirstResult(offset).setMaxResults(pageSize).list(); return result; } }); return list; } /** * 使用hql语句进行分页查询 * @param hql 需要查询的hql语句 * @param values 如果hql有多个个参数需要传入,values就是传入hql的参数数组 * @param offset 第一条记录索引 * @param pageSize 每页需要显示的记录数 * @return 当前页的所有记录 */ public List findByPage(final String hql, final Object[] values,final int offset, final int pageSize) { //通过一个HibernateCallback对象来执行查询 List list = getHibernateTemplate().executeFind(new HibernateCallback() { //实现HibernateCallback接口必须实现的方法 public Object doInHibernate(Session session)throws HibernateException, SQLException { //执行Hibernate分页查询 Query query = session.createQuery(hql); //为hql语句传入参数 for (int i = 0 ; i < values.length ; i++) { query.setParameter( i, values[i]); } List result = query.setFirstResult(offset).setMaxResults(pageSize).list(); return result; } }); return list; } }
在上面代码中所看到的,当Dao的实现类继承了hibernateDaoSupport之后,就可以非常容易的活的hibernateTemplate 实例,一旦拥有了hibernateTemplate实例,大部分持久化操作就可以通过一行代码来实现。
7 实现dao层 –>DAO的部署
所谓DAO的部署,也就是配置applicationContext.xml文件。
DAO组件以Hibernate和Spring为基础,由Spring容器负责生成并管理DAO组件。Spring容器负责为DAO组件注入其运行的基础:SessionFactory。Spring为整合Hibernate提供了大量工具类。通过LocalSessionFactoryBean类,可以将SessionFactory纳入IoC容器内。在配置SessionFactory之前,必须为其提供对应的数据源。示例中使用C3PO数据源。Spring容器也负责管理数据源。在Spring容器中配置数据源的代码如下:
对于继承hibernateDaoSupport的DAO实现类,只需要为其注入SessionFactory即可。由于所有的Dao组件实现类都需要注入SessionFactory引用,因此可以使用Bean继承简化Dao组件的配置。这个程序将所有的DAO组件配置在单独的配置文件中。daoContext.xml中配置片段如下
Web.xml中的片段:
8 实现service层
业务逻辑组件是DAO组件的门面,它需要依赖与DAO组件。每个业务方法要涉及多个DAO操作,其中DAO操作是单个的数据记录的操作。往往每个业务方法需要多条记录的访问。业务逻辑组件面向DAO接口编程,可让业务逻辑组件从DAO组件实现分离。因此业务逻辑组件只关心业务逻辑的实现,无需关心数据访问逻辑的实现。
9 service层是做什么的
业务逻辑组件负责实现系统所需的业务方法。系统有多少个业务需求,业务
逻辑组件就提供多少个对应方法。service层只负责业务逻辑上的变化,持久层
上的变化交给Dao负责,因此业务逻辑组件必须依赖与Dao组件。
以EmpManager类与Dao接口组件的关系可以看出中间的EmpManager类依赖所有
的Dao接口组件。
10 service层与事务管理
什么是事务管理?一个Java EE应用系统中,系统的事务管理负责管理业务逻辑组件里的业务逻辑方法。只有对业务逻辑方法添加事务管理才有实际的意义,对于单个DAO方法中基本的增删改查增加事务管理是没有意义的。
11 任务调度
绝大部分系统中都会涉及到“任务的自动调度”,让一些任务自动执行。这些任务每隔一段时间需要执行一次,也可能需要在指定的时间点自动执行,这些任务的自动执行必须使用任务的自动调度。JDK为任务调度提供了Timer支持,但他的性能不好。一般企业级系统会使用Quartz(石英)这个开源框架,借助于他的支持既可以实现简单的任务调度,也可以执行复杂的任务调度。由于这是一个开源框架,他的源代码是公开的,你也可以对他进行企业级的二次开发,这里有相关介绍可参考:http://www.oschina.net/question/129540_111323
Quartz提供一个quartz.properties的配置文件。通过该配置文件可以修改框架运行时的环境。默认使用quartz-1.8.4.jar里的quartz.properties文件
12 Quartz 中的作业问题
这里的作业是一个执行指定任务的Java类,当Quartz调用某个Java任务执行时,实际上就是执行该任务对象的execute()方法,Quartz里的作业类需要实现org.quartz.Job接口,该接口包含一个execute()方法,方法体是被调度的作业体。实现这个方法后,当Quartz调度该作业运行时,该execute()方法就会自动运行起来。此外在quartz中还有触发器和调度器这两个重要的内容,但与本文章关联不太大,读者可以自己认真看看。
在Spring中使用Quartz。Spring的任务调度抽象层简化了任务调度,在Quartz的基础上提供
了更好的调度抽象。需要创建Quartz作业对应的Bean,有两种方法,如下:
1. 利用JobDetailBean包装QuartzJobBean子类的实例。
2. 利用MethodInvokingJobDetailFactoryBean工厂Bean包装普通java对象。
采用这两种方法都可以创建一个Quartz所需要的JobDetailBean,也就是Quartz所需的任务对
象。第一种方法需要作业Bean类继承QuartzJobBean类。第二种方法则不需要继承父类,直接配置
即可。配置MethodInvokingJobDetailFactoryBean需要指定一下两个属性:
1. targetObject:指定包含任务执行体的Bean实例。
2. targetMethod:指定将指定Bean实例的该方法包装成任务执行体。
示例程序选择第一种方法:extends QuartzJobBean,配置代码如下:
<bean id="cronTriggerPay" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail"> <!-- 使用嵌套Bean的方式来定义任务Bean --> <bean class="org.springframework.scheduling.quartz.JobDetailBean"> <!-- 指定任务Bean的实现类 --> <property name="jobClass" value="com.hongbo.attsystem.schedule.PayJob"/> <!-- 为任务Bean注入属性 --> <property name="jobDataAsMap"> <map> <entry key="empMgr" value-ref="empManager"/> </map> </property> </bean> </property> <!-- 指定Cron表达式:每月3日2时启动 --> <property name="cronExpression" value="0 0 2 3 * ? *"/> </bean> <!-- 定义触发器来管理任务Bean --> <bean id="cronTriggerPunch" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail"> <!-- 使用嵌套Bean的方式来定义任务Bean --> <bean class="org.springframework.scheduling.quartz.JobDetailBean"> <!-- 指定任务Bean的实现类 --> <property name="jobClass" value="com.hongbo.attsystem.schedule.PunchJob"/> <!-- 为任务Bean注入属性 --> <property name="jobDataAsMap"> <map> <entry key="empMgr" value-ref="empManager"/> </map> </property> </bean> </property> <!-- 指定Cron表达式:周一到周五7点、12点执行调度 --> <property name="cronExpression" value="0 0 7,12 ? * MON-FRI"/> </bean>
完成上述步骤后,我们要做的事是:实现系统Web层。前面的内容已经实现了所有中间层的内容(到这里你应该知道什么是中间层内容)。
13 Sping 整合 Struts2
为了在系统中启用Struts2,首先要在web.xml文件中配置Struts2的核心Filter(核心拦截器),让该Filter拦截器拦截所有用户请求。即在web.xml中添加如下配置片段:
Tip3:web.xml是整个web应用程序的入口文件。Tomcat和一些运行容器他们要首先读取的文件。
核心拦截器启动后,用户请求将被纳入Struts2的管理中。FilterDispatcher会调用用户实现的Action来处理用户请求。Struts2的Action是用户请求和业务逻辑方法之间的纽带,Action需要调用业务逻辑组件的方法来处理用户请求,但是作为SSH项目,系统的业务逻辑组件由Spring管理,所以需要在web.xml文件中使用load-on-startup的Servlet或Listener来初始化Spring容器,故需要在web.xml中添加如下配置片段:
配置文件使用ContextLoaderListener来初始化Spring容器,并指定了applicationContext.xml、daoContext.xml文件作为Spring配置文件。Spring容器初始化完成后,Struts2的Action会通过自动装配策略来访问Spring容器中的Bean。例如:Action中包含一个setName()方法,如果Sping容器中有一个id为name的Bean实例,则该Bean将会被自动装配给该Action。前面定义了两个业务逻辑组件,他们在Spring容器中的id分别为:empManager和mgrManager。
14 控制器处理顺序
控制器接受用户请求,将用户请求参数解析出来;然后调用业务逻辑方法来处理用户请求。请求处理完成,控制器将处理结果通过JSP页面呈献给用户。针对Struts2,控制器实际由两个部分组成:系统核心控制器:FilterDispatcher和业务控制器Action。
1. 在web.xml中开启Struts2
2. Main.jsp中给出登录连接或Tomcat直接定位到login.jsp页(注:此处源码错误)
3. 使用定义的action
这个action是在struts.xml中定义的; line 60,processLogin
4. struts.xml定义action;他的name是processLogin
5. LoginAction返回定义的常量字符
返回定义的常量字符对应第四步中配置定向的页面。
至此众多的控制器处理流程中的一个完成。
15 action通配符与路径跳转
如在项目中查看本人基本工资的功能,在struts.xml文件中配置的一段代码如下:
<!-- 查看本人工资的Action 使用通配符的方式配置Action跳转的路径 --> <action name="view*Salary" class="com.hongbo.attsystem.action.ViewSalaryAction"> <interceptor-ref name="basicStack"/> <interceptor-ref name="empAuth"/> <result>/content/{1}/viewSalary.jsp</result> </action>对应在 mgrheader.jsp 中调用是这样的:
<td width="104"> <div align="center"> <%--使用通配符的方式配置Action跳转的路径 还有很多其他action类似,具体见struts.xml--%> <%--<a href="viewmanagerSalary.action">查看历史工资</a>--%> <a href="viewmanagerSalary">查看历史工资</a><%--".action有与无没有影响"--%> </div> </td>16 拦截器与权限管理
在Action的配置代码中,每个<action …/>元素中都配置了一个权限检查的拦截器,这个拦截器负责检查当前用户权限,检查该权限是否足够处理实际请求。如果权限不够系统将退回登录页面。在这个系统中,员工与经理分别提供不同的拦截器,员工的拦截器只要求HttpSession里的level属性不为null,且level属性为emp或mgr都可以。员工的权限检查拦截器代码如下:
package com.hongbo.attsystem.action.authority; import com.hongbo.attsystem.action.WebConstant; import com.opensymphony.xwork2.*; import com.opensymphony.xwork2.interceptor.*; /** * 权限拦截器,员工。 * User: Yangcl * Date: 13-5-16 * Time: 下午2:26 * To change this template use File | Settings | File Templates. */ public class EmpAuthorityInterceptor extends AbstractInterceptor { public String intercept(ActionInvocation invocation) throws Exception { //创建ActionContext实例 ActionContext ctx = ActionContext.getContext(); //获取HttpSession中的level属性 String level = (String)ctx.getSession().get(WebConstant.LEVEL); //如果level不为null,且level为emp或mgr if (level != null && (level.equals(WebConstant.EMP_LEVEL) || level.equals(WebConstant.MGR_LEVEL))) { return invocation.invoke(); } else { return Action.LOGIN; } } }
大家应该注意这两个类:EmpAuthorityInterceptor.java和MgrAuthorityInterceptor.java。在EmpAuthorityInterceptor.java的代码中可以看到,如果HttpSession里的level属性不为null,且level属性为emp或着mgr时,该拦截器就会放行该请求,就是说这个请求就可以得到正常处理;否则系统就会直接返回“login”字符串,让用户重新登录。经理角色与他类似,不同之处在于它需要HttpSession里的level属性为mgr。
他们在Struts.xml中的配置方式是:
对于默认拦截器栈的配置的作用是简化了引用过程,提供方便。
17 验证码
com.hongbo.attsystem.AuthCode. AuthImg.java中针对这段代码:
@WebServlet(urlPatterns={"/content/authImg.jsp"})给予一下解释。如果你想使用annotation,你必须引入javax.servlet.annotation.*;。注释声明在servlet3.0后出现的,对应在pom文件中定义的依赖如下:
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency>
你在整个项目工程中可能找不到这个页面:authImg.jsp。但这句话是什么意思呢?他是要说
在Servlet上设置@WebServlet标注,容器就会自动读取当中的信息。上面的@WebServlet告诉容器,如果请求的URL是content目录下的authImg.jsp文件,则由AuthImg的实例提供服务。事实上AuthImg.java是一个servlet类。更多关于servlet3.0的新特性你可以在下面的链接中找到:http://book.51cto.com/art/201204/329134.htm。
下图是他的使用位置login.jsp,line39。
18 struts 2 的数据验证
1. 使用编码方式进行验证。业务控制器Action继承ActionSupport类,重写public void validate()方法。在该方法中进行数据验证。若Action中处理业务的方法为test,则可以编写public void validateTest()方法,来对test方法的数据进行验证。(系统在进行validateTest方法后,会接着执行validate方法,然后才执行相应的业务代码。) 若严重出错,则可以调用addFieldError或者调用addActionError方法,增加相应的错误提示信息。
2. 使用配置xml文件进行验证。验证文件的名字为:xxxAction-validation.xml。验证的方式包含字段验证和非字段验证。其中字段验证表示对某个字段进行某些类型的验证。非字段验证表示用某个类型的验证来验证,某个字段。两种方式底层实现一样,只是表现方式不同,字段验证方式比较直观。验证的类型有一下几种:required , requiredstring , int , date , double , expression , fieldexpression ,email , url , visitor , conversion , stringLength , regex(正则表达式)。对验证类型可以指定shourt-circuit参数为true,来执行短路验证。如果Action中执行业务的方法为test,则可以通过编写×××Action-test-validation.xml方法来对test方法的数据进行验证。且执行完test方法的私有验证文件后,还会执行默认的验证文件×××Action-test-validation.xml的验证。
3. struts2进行客户端的验证。首先需要使用struts2的标签库,且标签库的theme属性不能为simple,然后设置标签的validate属性为true。
注意:struts2的客户端验证依赖于服务器的验证配置文件。
这里使用的是第二种方法。要说明的是xxxAction-validation.xml文件在 IDEA 中怎么放入到对应的Action目录下。这三个xml配置文件放入到com.hongbo.attsystem.action下,struts2会自动加载他们。但是 IDEA 不允许配置文件放在代码包路径下。但我们可以通过在 resources 文件夹下创建对应的目录路径来达到我们的目的。如下图所示,当我们用 Maven 去 install 这个程序的时候,在resources下的目录结构中的配置文件会自动放入到对应的代码路径中。说了这么多我们看一下 LoginAction-validation.xml对应的代码:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd"> <validators> <field name="manager.name"> <field-validator type="requiredstring"> <message>用户名必填!</message> </field-validator> <field-validator type="regex"> <param name="expression"><![CDATA[(\w{4,25})]]></param> <message>您输入的用户名只能是字母和数字,且长度必须在4到25之间</message> </field-validator> </field> <field name="manager.pass"> <field-validator type="requiredstring"> <message>密码必填!</message> </field-validator> <field-validator type="regex"> <param name="expression"><![CDATA[(\w{4,25})]]></param> <message>您输入的密码只能是字母和数字,且长度必须在4到25之间</message> </field-validator> </field> <field name="vercode"> <field-validator type="requiredstring"> <message>验证码必填!</message> </field-validator> <field-validator type="regex"> <param name="expression"><![CDATA[(\w{6,6})]]></param> <message>您输入的验证码只能是字母和数字,且长度必须在6位</message> </field-validator> </field> </validators>对应在系统中表现是这样的,如下图:
到此为止,SSH2项目结构解析完成。欢迎您到访我的个人主页交流更多的问题:http://www.open-open.com/home/135360。关于项目的源代码程序我会发布在CSDN上,当然收的分也比较高些,毕竟这些东西调试、整理很辛苦,花费了我很长时间。其实换个方向想想的话,你买一本书也要花费5、6十块钱,相比来讲,这点分还是很少很划算的。针对您在使用IDEA这个开发环境中遇到的问题您可以在这篇文章的下面给我留言,我会耐心帮您解答。
本文谢绝任何形势转载,作者保留著作所有权,如果发现您的不文明转载将追究您的法律责任.
2013年6月6日晚。
系统首页:
完整资源地址:http://download.csdn.net/detail/breatheryang/5549101
对于如何使用IDEA您可以给我留言也可以参考这篇文章:
http://www.open-open.com/home/space-135360-do-blog-id-9698.html