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.baiduhome.net/home/135360。關于項目的源代碼程序我會發布在CSDN上,當然收的分也比較高些,畢竟這些東西調試、整理很辛苦,花費了我很長時間。其實換個方向想想的話,你買一本書也要花費5、6十塊錢,相比來講,這點分還是很少很劃算的。針對您在使用IDEA這個開發環境中遇到的問題您可以在這篇文章的下面給我留言,我會耐心幫您解答。
本文謝絕任何形勢轉載,作者保留著作所有權,如果發現您的不文明轉載將追究您的法律責任.
2013年6月6日晚。
系統首頁:
完整資源地址:http://download.csdn.net/detail/breatheryang/5549101
對于如何使用IDEA您可以給我留言也可以參考這篇文章:
http://www.baiduhome.net/home/space-135360-do-blog-id-9698.html