2005年11月21日
#
摘要: 我們開發程序的目的是為了完成業務功能, 理想的情況下程序中的每一條語句都應該是與業務直接相關的, 例如程序中不應該出現連接數據庫, 讀取某個字段等純技術性的操作, 而應該是得到用戶A的基本信息等具有業務含義的操作. dao(data access object)層存在的意。。。。。
閱讀全文
數據庫設計的范式大綱
第一范式:
對于表中的每一行,必須且僅僅有唯一的行值.在一行中的每一列僅有唯一的值并且具有原子性.
第二范式:
第二范式要求非主鍵列是主鍵的子集,非主鍵列活動必須完全依賴整個主鍵。主鍵必須有唯一性的元素,一個主鍵可以由一個或更多的組成唯一值的列組成。一旦創建,主鍵無法改變,外鍵關聯一個表的主鍵。主外鍵關聯意味著一對多的關系.
第三范式:
第三范式要求非主鍵列互不依賴.
第四范式:
第四范式禁止主鍵列和非主鍵列一對多關系不受約束
第五范式:
第五范式將表分割成盡可能小的塊,為了排除在表中所有的冗余.
下面先討論前3個范式:
引言
數據庫的設計范式是數據庫設計所需要滿足的規范,滿足這些規范的數據庫是簡潔的、結構明晰的,同時,不會發生插入
(insert)、刪除(delete)和更新(update)操作異常。反之則是亂七八糟,不僅給數據庫的編程人員制造麻煩,而且面目可憎,可能存儲了 大量不需要的冗余信息。
設計范式是不是很難懂呢?非也,大學教材上給我們一堆數學公式我們當然看不懂,也記不住。所以我們很多人就根本不按照范式來設計數據庫。
實質上,設計范式用很形象、很簡潔的話語就能說清楚,道明白。本文將對范式進行通俗地說明,并以筆者曾經設計的一個簡單論壇的數據庫為例來講解怎樣將這些范式應用于實際工程。
范式說明
第一范式(1NF):數據庫表中的字段都是單一屬性的,不可再分。這個單一屬性由基本類型構成,包括整型、實數、字符型、邏輯型、日期型等。
例如,如下的數據庫表是符合第一范式的:
而這樣的數據庫表是不符合第一范式的:
字段1
|
字段2
|
字段3
|
字段4
|
|
|
字段3.1
|
字段3.2
|
|
|
|
|
|
|
很顯然,在當前的任何關系數據庫管理系統(DBMS)中,傻瓜也不可能做出不符合第一范式的數據庫,因為這些DBMS不允許你把數據庫表的一列再分成二列或多列。因此,你想在現有的DBMS中設計出不符合第一范式的數據庫都是不可能的。
第二范式(2NF):數據庫表中不存在非關鍵字段對任一候選關鍵字段的部分函數依賴(部分函數依賴指的是存在組合關鍵字中的某些字段決定非關鍵字段的情況),也即所有非關鍵字段都完全依賴于任意一組候選關鍵字。
假定選課關系表為SelectCourse(學號, 姓名, 年齡, 課程名稱, 成績, 學分),關鍵字為組合關鍵字(學號, 課程名稱),因為存在如下決定關系:
(學號, 課程名稱) → (姓名, 年齡, 成績, 學分)
這個數據庫表不滿足第二范式,因為存在如下決定關系:
(課程名稱) → (學分)
(學號) → (姓名, 年齡)
即存在組合關鍵字中的字段決定非關鍵字的情況。
由于不符合2NF,這個選課關系表會存在如下問題:
(1) 數據冗余:
同一門課程由n個學生選修,"學分"就重復n-1次;同一個學生選修了m門課程,姓名和年齡就重復了m-1次。
(2) 更新異常:
若調整了某門課程的學分,數據表中所有行的"學分"值都要更新,否則會出現同一門課程學分不同的情況。
(3) 插入異常:
假設要開設一門新的課程,暫時還沒有人選修。這樣,由于還沒有"學號"關鍵字,課程名稱和學分也無法記錄入數據庫。
(4) 刪除異常:
假設一批學生已經完成課程的選修,這些選修記錄就應該從數據庫表中刪除。但是,與此同時,課程名稱和學分信息也被刪除了。很顯然,這也會導致插入異常。
把選課關系表SelectCourse改為如下三個表:
學生:Student(學號, 姓名, 年齡);
課程:Course(課程名稱, 學分);
選課關系:SelectCourse(學號, 課程名稱, 成績)。
這樣的數據庫表是符合第二范式的,消除了數據冗余、更新異常、插入異常和刪除異常。
另外,所有單關鍵字的數據庫表都符合第二范式,因為不可能存在組合關鍵字。
第三范式(3NF):在第二范式的基礎上,數據表中如果不存在非關鍵字段對任一候選關鍵字段的傳遞函數依賴則符合第三范式。所謂傳遞函數依賴,指的是如
果存在"A → B → C"的決定關系,則C傳遞函數依賴于A。因此,滿足第三范式的數據庫表應該不存在如下依賴關系:
關鍵字段 → 非關鍵字段x → 非關鍵字段y
假定學生關系表為Student(學號, 姓名, 年齡, 所在學院, 學院地點, 學院電話),關鍵字為單一關鍵字"學號",因為存在如下決定關系:
(學號) → (姓名, 年齡, 所在學院, 學院地點, 學院電話)
這個數據庫是符合2NF的,但是不符合3NF,因為存在如下決定關系:
(學號) → (所在學院) → (學院地點, 學院電話)
即存在非關鍵字段"學院地點"、"學院電話"對關鍵字段"學號"的傳遞函數依賴。
它也會存在數據冗余、更新異常、插入異常和刪除異常的情況,讀者可自行分析得知。
把學生關系表分為如下兩個表:
學生:(學號, 姓名, 年齡, 所在學院);
學院:(學院, 地點, 電話)。
這樣的數據庫表是符合第三范式的,消除了數據冗余、更新異常、插入異常和刪除異常。
鮑依斯-科得范式(BCNF):在第三范式的基礎上,數據庫表中如果不存在任何字段對任一候選關鍵字段的傳遞函數依賴則符合第三范式。
假設倉庫管理關系表為StorehouseManage(倉庫ID,
存儲物品ID, 管理員ID, 數量),且有一個管理員只在一個倉庫工作;一個倉庫可以存儲多種物品。這個數據庫表中存在如下決定關系:
(倉庫ID, 存儲物品ID) →(管理員ID, 數量)
(管理員ID, 存儲物品ID) → (倉庫ID, 數量)
所以,(倉庫ID, 存儲物品ID)和(管理員ID, 存儲物品ID)都是StorehouseManage的候選關鍵字,表中的唯一非關鍵字段為數量,它是符合第三范式的。但是,由于存在如下決定關系:
(倉庫ID) → (管理員ID)
(管理員ID) → (倉庫ID)
即存在關鍵字段決定關鍵字段的情況,所以其不符合BCNF范式。它會出現如下異常情況:
(1) 刪除異常:
當倉庫被清空后,所有"存儲物品ID"和"數量"信息被刪除的同時,"倉庫ID"和"管理員ID"信息也被刪除了。
(2) 插入異常:
當倉庫沒有存儲任何物品時,無法給倉庫分配管理員。
(3) 更新異常:
如果倉庫換了管理員,則表中所有行的管理員ID都要修改。
把倉庫管理關系表分解為二個關系表:
倉庫管理:StorehouseManage(倉庫ID, 管理員ID);
倉庫:Storehouse(倉庫ID, 存儲物品ID, 數量)。
這樣的數據庫表是符合BCNF范式的,消除了刪除異常、插入異常和更新異常。
范式應用
我們來逐步搞定一個論壇的數據庫,有如下信息:
(1) 用戶:用戶名,email,主頁,電話,聯系地址
(2) 帖子:發帖標題,發帖內容,回復標題,回復內容
第一次我們將數據庫設計為僅僅存在表:
用戶名
|
email
|
主頁
|
電話
|
聯系地址
|
發帖標題
|
發帖內容
|
回復標題
|
回復內容
|
這個數據庫表符合第一范式,但是沒有任何一組候選關鍵字能決定數據庫表的整行,唯一的關鍵字段用戶名也不能完全決定整個元組。我們需要增加"發帖ID"、"回復ID"字段,即將表修改為:
用戶名
|
email
|
主頁
|
電話
|
聯系地址
|
發帖ID
|
發帖標題
|
發帖內容
|
回復ID
|
回復標題
|
回復內容
|
這樣數據表中的關鍵字(用戶名,發帖ID,回復ID)能決定整行:
(用戶名,發帖ID,回復ID) → (email,主頁,電話,聯系地址,發帖標題,發帖內容,回復標題,回復內容)
但是,這樣的設計不符合第二范式,因為存在如下決定關系:
(用戶名) → (email,主頁,電話,聯系地址)
(發帖ID) → (發帖標題,發帖內容)
(回復ID) → (回復標題,回復內容)
即非關鍵字段部分函數依賴于候選關鍵字段,很明顯,這個設計會導致大量的數據冗余和操作異常。
我們將數據庫表分解為(帶下劃線的為關鍵字):
(1) 用戶信息:用戶名,email,主頁,電話,聯系地址
(2) 帖子信息:發帖ID,標題,內容
(3) 回復信息:回復ID,標題,內容
(4) 發貼:用戶名,發帖ID
(5) 回復:發帖ID,回復ID
這樣的設計是滿足第1、2、3范式和BCNF范式要求的,但是這樣的設計是不是最好的呢?
不一定。
觀察可知,第4項"發帖"中的"用戶名"和"發帖ID"之間是1:N的關系,因此我們可以把"發帖"合并到第2項的"帖子信息"中;第5項"回復"中的 "發帖ID"和"回復ID"之間也是1:N的關系,因此我們可以把"回復"合并到第3項的"回復信息"中。這樣可以一定量地減少數據冗余,新的設計為:
(1) 用戶信息:用戶名,email,主頁,電話,聯系地址
(2) 帖子信息:用戶名,發帖ID,標題,內容
(3) 回復信息:發帖ID,回復ID,標題,內容
數據庫表1顯然滿足所有范式的要求;
數據庫表2中存在非關鍵字段"標題"、"內容"對關鍵字段"發帖ID"的部分函數依賴,即不滿足第二范式的要求,但是這一設計并不會導致數據冗余和操作異常;
數據庫表3中也存在非關鍵字段"標題"、"內容"對關鍵字段"回復ID"的部分函數依賴,也不滿足第二范式的要求,但是與數據庫表2相似,這一設計也不會導致數據冗余和操作異常。
由此可以看出,并不一定要強行滿足范式的要求,對于1:N關系,當1的一邊合并到N的那邊后,N的那邊就不再滿足第二范式了,但是這種設計反而比較好!
對于M:N的關系,不能將M一邊或N一邊合并到另一邊去,這樣會導致不符合范式要求,同時導致操作異常和數據冗余。
對于1:1的關系,我們可以將左邊的1或者右邊的1合并到另一邊去,設計導致不符合范式要求,但是并不會導致操作異常和數據冗余。
結論
滿足范式要求的數據庫設計是結構清晰的,同時可避免數據冗余和操作異常。這并意味著不符合范式要求的設計一定是錯誤的,在數據庫表中存在1:1或1:N關系這種較特殊的情況下,合并導致的不符合范式要求反而是合理的。
在我們設計數據庫的時候,一定要時刻考慮范式的要求。
摘要: 可能大家對PROPAGATION_NESTED還不怎么了解,覺得有必要再補充一下^_^!
閱讀全文
摘要: 大家可能在spring中經常看到這樣的定義:
PROPAGATION_REQUIRED,readOnlyPROPAGATION_REQUIRED
估計有好多朋友還沒有弄清楚里面的值的意思,仔細看完下面應該知道自己什么情況下面應該使用什么樣的聲明。^_^
閱讀全文
Spring也提供了聲明式事務管理。這是通過Spring AOP實現的。
Spring
中進行事務管理的通常方式是利用AOP(面向切片編程)的方式,為普通java類封裝事務控制,它是通過動態代理實現的,由于接口是延遲實例化的,
spring在這段時間內通過攔截器,加載事務切片。原理就是這樣,具體細節請參考jdk中有關動態代理的文檔。本文主要講解如何在spring中進行事
務控制。
動態代理的一個重要特征是,它是針對接口的,所以我們的dao要通過動態代理來讓spring接管事務,就必須在dao前面抽象出一個接口,當然如果沒有這樣的接口,那么spring會使用CGLIB來解決問題,但這不是spring推薦的方式,所以不做討論.
從考慮EJB CMT和Spring聲明式事務管理的相似以及不同之處出發是很有益的。 它們的基本方法是相似的:都可以指定事務管理到單獨的方法;如果需要可以在事務上 下文調用setRollbackOnly()方法。不同之處如下:
不象EJB CMT綁定在JTA上,Spring聲明式事務管理可以在任何環境下使用。 只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事務機制一起工作
Spring可以使聲明式事務管理應用到普通Java對象,不僅僅是特殊的類,如EJB
Spring提供聲明式回滾規則:EJB沒有對應的特性, 我們將在下面討論這個特性。回滾可以聲明式控制,不僅僅是編程式的
Spring允許你通過AOP定制事務行為。例如,如果需要,你可以在事務 回滾中插入定制的行為。你也可以增加任意的通知,就象事務通知一樣。使用 EJB CMT,除了使用setRollbackOnly(),你沒有辦法能 夠影響容器的事務管理
Spring不提供高端應用服務器提供的跨越遠程調用的事務上下文傳播。如 果你需要這些特性,我們推薦你使用EJB。然而,不要輕易使用這些特性。通常我 們并不希望事務跨越遠程調用
回滾規則的概念是很重要的:它們使得我們可以指定哪些異常應該發起自 動回滾。我們在配置文件中,而不是Java代碼中,以聲明的方式指定。因此,雖然我們仍 然可以編程調用TransactionStatus對象的 setRollbackOnly()方法來回滾當前事務,多數時候我們可以 指定規則,如MyApplicationException應該導致回滾。 這有顯著的優點,業務對象不需要依賴事務基礎設施。例如,它們通常不需要引 入任何Spring API,事務或其他任何東西。
EJB的默認行為是遇到系統異常(通常是運行時異常), EJB容器自動回滾事務。EJB CMT遇到應用程序異常 (除了java.rmi.RemoteException外的checked異常)時不 會自動回滾事務。雖然Spring聲明式事務管理沿用EJB的約定(遇到unchecked 異常自動回滾事務),但是這是可以定制的。
按照我們的測試,Spring聲明式事務管理的性能要勝過EJB CMT。
通
常通過TransactionProxyFactoryBean設置Spring事務代理。我們需
要一個目標對象包裝在事務代理中。這個目標對象一般是一個普通Java對象的bean。當我
們定義TransactionProxyFactoryBean時,必須提供一個相關的 PlatformTransactionManager的引用和事務屬性。 事務屬性含有上面描述的事務定義。
<bean id="petStore"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="target"><ref bean="petStoreTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED,-MyCheckedException</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
事
務代理會實現目標對象的接口:這里是id為petStoreTarget的bean。(使用
CGLIB也可以實現具體類的代理。只要設置proxyTargetClass屬性為true就可以。
如果目標對象沒有實現任何接口,這將自動設置該屬性為true。通常,我們希望面向接口而不是
類編程。)使用proxyInterfaces屬性來限定事務代理來代 理指定接口也是可以的(一般來說是個好想法)。也可以通過從 org.springframework.aop.framework.ProxyConfig繼承或所有AOP代理工廠共享 的屬性來定制TransactionProxyFactoryBean的行為。
這里的transactionAttributes屬性定義在 org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource 中的屬性格式來設置。這個包括通配符的方法名稱映射是很直觀的。注意 insert*的映射的值包括回滾規則。添加的-MyCheckedException 指定如果方法拋出MyCheckedException或它的子類,事務將 會自動回滾。可以用逗號分隔定義多個回滾規則。-前綴強制回滾,+前綴指定提交(這允許即使拋出unchecked異常時也可以提交事務,當然你自己要明白自己 在做什么)。
TransactionProxyFactoryBean允許你通過 “preInterceptors”和“postInterceptors”屬性設置“前”或“后”通知來提供額外的 攔截行為。可以設置任意數量的“前”和“后”通知,它們的類型可以是 Advisor(可以包含一個切入點), MethodInterceptor或被當前Spring配置支持的通知類型 (例如ThrowAdvice, AfterReturningtAdvice或BeforeAdvice, 這些都是默認支持的)。這些通知必須支持實例共享模式。如果你需要高級AOP特 性來使用事務,如有狀態的maxin,那最好使用通用的 org.springframework.aop.framework.ProxyFactoryBean, 而不是TransactionProxyFactoryBean實用代理創建者。
也可以設置自動代理:配置AOP框架,不需要單獨的代理定義類就可以生成類的 代理。
附兩個spring的事務配置例子:
<prop key="add">
PROPAGATION_REQUIRES_NEW, -MyException
</prop>
注:上面的意思是add方法將獨占一個事務,當事務處理過程中產生MyException異常或者該異常的子類將回滾該事務。
<prop key="loadAll">
PROPAGATION_SUPPORTS, ISOLATION_READ_COMMITED, Readonly
</prop>
注:表示loadAll方法支持事務,而且不會讀取沒有提交事務的數據。它的數據為只讀(這樣有助于提高讀取的性能)
附A Spring中的所有事務策略
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED_NEW
PROPAGATION_SUPPORTS
附B Spring中所有的隔離策略:
ISOLATION_DEFAULT
ISOLATION_READ_UNCOMMITED
ISOLATION_COMMITED
ISOLATION_REPEATABLE_READ
ISOLATION_SERIALIZABLE
Yes,
.
The WebWork development team (Jason and I) have been working with the
Struts development team (Don Brown and Ted Husted) and have come to the
conclusion that the best thing for Java community would be to merge
WebWork in to Struts.
Read Ted's email here,
but the gist of it is this: WebWork is a great technology, and Struts
is a great community. It's a perfect match and bringing the two
together will only be better for WebWork and Struts users alike. The
only down side for me is that I'll be working less with OpenSymphony,
but I believe that is a small price for all the great benefits that
come from this merger.
Just to be clear, WebWork is not going away.
WebWork 2.2 is still coming out any day now, and there may even be a
WebWork 2.3. But new minor/major versions won't be coming out under the
WebWork name for much longer. Instead, they will be released under the
Struts incubator program with the intent to eventually become Struts
Action Framework 2.0.
So don't worry, WebWork 2.1.8, 2.2.1, and other bug fix releases
will continue to come out and we will support the WebWork community as
long as needed. In addition, we'll make compatibility with both Struts
and WebWork a high priority, so future releases may help with that. To
be clear: no one is leaving WebWork and it will continue to be supported for a very long time.
With this renewed energy, larger development team, and larger
community, the combined efforts of Struts and WebWork will surely make
the Struts platform the easiest, fastest, and most powerful Java web
framework available. We hope that all the WebWork users and developers
are as excited about this as we are and are ready to take WebWork to
the next level.
原文地址:http://www.opensymphony.com/webwork/
以前一直在struts和webwork之間猶豫,看來struts氣數未盡呀。apache組織還是比較只值得信賴的。呵呵!
由于項目里面由需要一個form可以提交多個action(本來可以用
dispatch值來實現,后來考慮到要使用validator框架驗證)。后來考慮的方案為使用js來控制form的流向,例如
form.action='/bookstore/checkId.do'
不過新的問題來了!如何能不用hardcode而拿到我要的action的實際路徑呢?比如我定義的struts-config文件里面的action是
path="/checkId"
但是實際解釋后的path是:
action='/bookstore/checkId.do'
前
綴和后面的后綴.do都是根據你的項目部署的路徑和你在web.xml中配置的mapping的后綴有關系,如果我把內容寫死到jsp中那以后我要是想把
checkId.do改成checkId.action那就要更改jsp,由于struts本來提供的幾個taglib里面的
<html:form action="/checkId" >
綜合了一下決定還是自己寫個taglib來實現,其實只需要照著struts 中的 FormTag.java 文件依葫蘆畫瓢就可以了,一下為本人的代碼部分
StrutsActionCustomTag.java
package com.denis.framework.common.taglib.strutsActionExt;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.struts.taglib.TagUtils;
public class StrutsActionCustomTag extends TagSupport
{
protected String actionName = null;
public String getActionName()
{
return actionName;
}
public void setActionName(String actionName)
{
this.actionName = actionName;
}
public int doStartTag() throws JspException
{
StringBuffer results = new StringBuffer();
HttpServletResponse response =
(HttpServletResponse) this.pageContext.getResponse();
results.append(response.encodeURL( TagUtils.getInstance().getActionMappingURL( this.actionName , this.pageContext)) );
TagUtils.getInstance().write(pageContext, results.toString());
return (EVAL_BODY_INCLUDE);
}
public void release()
{
super.release();
this.actionName = null ;
}
}
tld定義部分
framework-struts.tld
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>framework</shortname>
<uri>http://www.denisFramework.org/Framework-Tag</uri>
<tag>
<name>getActionUrl</name>
<tagclass>com.denis.framework.common.taglib.strutsActionExt.StrutsActionCustomTag</tagclass>
<bodycontent>empty</bodycontent>
<attribute>
<name>actionName</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
ok ! 直接在jsp中如下使用即可取到action的真正路徑
<%
@ page language="java"%>
<%
@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean"%>
<%
@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html"%>
<%
@ taglib uri="/WEB-INF/framework-struts.tld" prefix="framework" %>
<html>
<head>
<title>JSP for loginForm form</title>
</head>
<body>
<framework:getActionUrl actionName="login" />
<html:form action="/login">
name : <html:text property="name"/><html:errors property="name"/><br/>
password : <html:password property="password"/><html:errors property="password"/><br/>
<html:submit/><html:cancel/>
</html:form>
</body>
</html>
大家要是有更好的解決方法希望能指正!謝謝!
Struts Recipes 的合著者 George Franciscus 將介紹另一個重大的
Struts 整合竅門 —— 這次是將 Struts 應用程序導入 Spring 框架。請跟隨 George,他將向您展示如何改變
Struts 動作,使得管理 Struts 動作就像管理 Spring beans 那樣。結果是一個增強的 web
框架,這個框架可以方便地利用 Spring AOP 的優勢。
您肯定已經聽說過控制反轉 (IOC)
設計模式,因為很長一段時間以來一直在流傳關于它的信息。如果您在任何功能中使用過 Spring
框架,那么您就知道其原理的作用。在本文中,我利用這一原理把一個 Struts 應用程序注入 Spring 框架,您將親身體會到 IOC
模式的強大。
將一個 Struts 應用程序整合進 Spring 框架具有多方面的優點。首先,Spring 是為解決一些關于
JEE 的真實世界問題而設計的,比如復雜性、低性能和可測試性,等等。第二,Spring 框架包含一個 AOP
實現,允許您將面向方面技術應用于面向對象的代碼。第三,一些人可能會說 Spring 框架只有處理 Struts 比 Struts 處理自己好。但是這是觀點問題,我演示三種將 Struts 應用程序整合到 Spring 框架的方法后,具體由您自己決定使用哪一種。
我所演示的方法都是執行起來相對簡單的,但是它們卻具有明顯不同的優點。我為每一種方法創建了一個獨立而可用的例子,這樣您就可以完全理解每種方法。
請參閱 下載 部分獲得完整例子源代碼。請參閱 參考資料,下載 Struts MVC 和 Spring 框架。
為什么 Spring 這么了不起?
Spring
的創立者 Rod Johnson 以一種批判的眼光看待 Java? 企業軟件開發,并且提議很多企業難題都能夠通過戰略地使用 IOC
模式(也稱作依賴注入)來解決。當 Rod 和一個具有奉獻精神的開放源碼開發者團隊將這個理論應用于實踐時,結果就產生了 Spring
框架。簡言之,Spring 是一個輕型的容器,利用它可以使用一個外部 XML 配置文件方便地將對象連接在一起。每個對象都可以通過顯示一個
JavaBean 屬性收到一個到依賴對象的引用,留給您的簡單任務就只是在一個 XML 配置文件中把它們連接好。
 | IOC 和 Spring
IOC 是一種使應用程序邏輯外在化的設計模式,所以它是被注入而不是被寫入客戶機代碼中。將 IOC 與接口編程應用結合,就像 Spring 框架那樣,產生了一種架構,這種架構能夠減少客戶機對特定實現邏輯的依賴。請參閱 參考資料 了解更多關于 IOC 和 Spring 的信息。 |
|
依
賴注入是一個強大的特性,但是 Spring 框架能夠提供更多特性。Spring
支持可插拔的事務管理器,可以給您的事務處理提供更廣泛的選擇范圍。它集成了領先的持久性框架,并且提供一個一致的異常層次結構。Spring
還提供了一種使用面向方面代碼代替正常的面向對象代碼的簡單機制。
Spring AOP 允許您使用攔截器 在一個或多個執行點上攔截應用程序邏輯。加強應用程序在攔截器中的日志記錄邏輯會產生一個更可讀的、實用的代碼基礎,所以攔截器廣泛用于日志記錄。您很快就會看到,為了處理橫切關注點,Spring AOP 發布了它自己的攔截器,您也可以編寫您自己的攔截器。
整合 Struts 和 Spring
與
Struts 相似,Spring 可以作為一個 MVC 實現。這兩種框架都具有自己的優點和缺點,盡管大部分人同意 Struts 在 MVC
方面仍然是最好的。很多開發團隊已經學會在時間緊迫的時候利用 Struts 作為構造高品質軟件的基礎。Struts
具有如此大的推動力,以至于開發團隊寧愿整合 Spring 框架的特性,而不愿意轉換成 Spring
MVC。沒必要進行轉換對您來說是一個好消息。Spring 架構允許您將 Struts 作為 Web 框架連接到基于 Spring
的業務和持久層。最后的結果就是現在一切條件都具備了。
在接下來的小竅門中,您將會了解到三種將 Struts MVC 整合到 Spring 框架的方法。我將揭示每種方法的缺陷并且對比它們的優點。 一旦您了解到所有三種方法的作用,我將會向您展示一個令人興奮的應用程序,這個程序使用的是這三種方法中我最喜歡的一種。
三個小竅門
接下來的每種整合技術(或者竅門)都有自己的優點和特點。我偏愛其中的一種,但是我知道這三種都能夠加深您對 Struts 和 Spring 的理解。在處理各種不同情況的時候,這將給您提供一個廣闊的選擇范圍。方法如下:
- 使用 Spring 的
ActionSupport
類整合 Structs - 使用 Spring 的
DelegatingRequestProcessor
覆蓋 Struts 的 RequestProcessor
- 將 Struts
Action
管理委托給 Spring 框架
裝載應用程序環境
無論您使用哪種技術,都需要使用 Spring 的 ContextLoaderPlugin
為 Struts 的 ActionServlet
裝載 Spring 應用程序環境。就像添加任何其他插件一樣,簡單地向您的 struts-config.xml 文件添加該插件,如下所示:
<plug-in className= "org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property= "contextConfigLocation" value="/WEB-INF/beans.xml"/> </plug-in>
|
前面已經提到過,在 下載 部分,您能夠找到這三個完全可使用的例子的完整源代碼。每個例子都為一個書籍搜索應用程序提供一種不同的 Struts 和 Spring 的整合方法。您可以在這里看到例子的要點,但是您也可以下載應用程序以查看所有的細節。
竅門 1. 使用 Spring 的 ActionSupport
手動創建一個 Spring 環境是一種整合 Struts 和 Spring 的最直觀的方式。為了使它變得更簡單,Spring 提供了一些幫助。為了方便地獲得 Spring 環境,org.springframework.web.struts.ActionSupport
類提供了一個 getWebApplicationContext()
方法。您所做的只是從 Spring 的 ActionSupport
而不是 Struts Action
類擴展您的動作,如清單 1 所示:
清單 1. 使用 ActionSupport 整合 Struts
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.DynaActionForm; import org.springframework.context.ApplicationContext; import org.springframework.web.struts.ActionSupport;
import ca.nexcel.books.beans.Book; import ca.nexcel.books.business.BookService;
public class SearchSubmit extends ActionSupport { |(1)
public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form; String isbn = (String) searchForm.get("isbn"); //the old fashion way //BookService bookService = new BookServiceImpl(); ApplicationContext ctx = getWebApplicationContext(); |(2) BookService bookService = (BookService) ctx.getBean("bookService"); |(3) Book book = bookService.read(isbn.trim());
if (null == book) { ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR,new ActionError ("message.notfound")); saveErrors(request, errors); return mapping.findForward("failure") ; }
request.setAttribute("book", book); return mapping.findForward("success"); } }
|
讓我們快速思考一下這里到底發生了什么。在 (1) 處,我通過從 Spring 的 ActionSupport
類而不是 Struts 的 Action
類進行擴展,創建了一個新的 Action
。在 (2) 處,我使用 getWebApplicationContext()
方法獲得一個 ApplicationContext
。為了獲得業務服務,我使用在 (2) 處獲得的環境在 (3) 處查找一個 Spring bean。
這
種技術很簡單并且易于理解。不幸的是,它將 Struts 動作與 Spring 框架耦合在一起。如果您想替換掉
Spring,那么您必須重寫代碼。并且,由于 Struts 動作不在 Spring 的控制之下,所以它不能獲得 Spring AOP
的優勢。當使用多重獨立的 Spring 環境時,這種技術可能有用,但是在大多數情況下,這種方法不如另外兩種方法合適。
竅門 2. 覆蓋 RequestProcessor
將 Spring 從 Struts 動作中分離是一個更巧妙的設計選擇。分離的一種方法是使用 org.springframework.web.struts.DelegatingRequestProcessor
類來覆蓋 Struts 的 RequestProcessor
處理程序,如清單 2 所示:
清單 2. 通過 Spring 的 DelegatingRequestProcessor 進行整合
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config> <form-beans> <form-bean name="searchForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="isbn" type="java.lang.String"/> </form-bean> </form-beans>
<global-forwards type="org.apache.struts.action.ActionForward"> <forward name="welcome" path="/welcome.do"/> <forward name="searchEntry" path="/searchEntry.do"/> <forward name="searchSubmit" path="/searchSubmit.do"/> </global-forwards>
<action-mappings> <action path="/welcome" forward="/WEB-INF/pages/welcome.htm"/> <action path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/> <action path="/searchSubmit" type="ca.nexcel.books.actions.SearchSubmit" input="/searchEntry.do" validate="true" name="searchForm"> <forward name="success" path="/WEB-INF/pages/detail.jsp"/> <forward name="failure" path="/WEB-INF/pages/search.jsp"/> </action>
</action-mappings>
<message-resources parameter="ApplicationResources"/>
<controller processorClass="org.springframework.web.struts. DelegatingRequestProcessor"/> |(1)
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in>
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="csntextConfigLocation" value="/WEB-INF/beans.xml"/> </plug-in> </struts-config>
|
我利用了 <controller>
標記來用 DelegatingRequestProcessor
覆蓋默認的 Struts RequestProcessor
。下一步是在我的 Spring 配置文件中注冊該動作,如清單 3 所示:
清單 3. 在 Spring 配置文件中注冊一個動作
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit" class="ca.nexcel.books.actions.SearchSubmit"> |(1) <property name="bookService"> <ref bean="bookService"/> </property> </bean> </beans>
|
注意:在 (1) 處,我使用名稱屬性注冊了一個 bean,以匹配 struts-config 動作映射名稱。SearchSubmit
動作揭示了一個 JavaBean 屬性,允許 Spring 在運行時填充屬性,如清單 4 所示:
清單 4. 具有 JavaBean 屬性的 Struts 動作
package ca.nexcel.books.actions;
import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.DynaActionForm;
import ca.nexcel.books.beans.Book; import ca.nexcel.books.business.BookService;
public class SearchSubmit extends Action { private BookService bookService; public BookService getBookService() { return bookService; }
public void setBookService(BookService bookService) { | (1) this.bookService = bookService; }
public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
DynaActionForm searchForm = (DynaActionForm) form; String isbn = (String) searchForm.get("isbn"); Book book = getBookService().read(isbn.trim()); |(2)
if (null == book) { ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR,new ActionError("message.notfound")); saveErrors(request, errors); return mapping.findForward("failure") ; }
request.setAttribute("book", book); return mapping.findForward("success"); }
}
|
在清單 4 中,您可以了解到如何創建 Struts 動作。在 (1) 處,我創建了一個 JavaBean 屬性。DelegatingRequestProcessor
自
動地配置這種屬性。這種設計使 Struts 動作并不知道它正被 Spring 管理,并且使您能夠利用 Sping
的動作管理框架的所有優點。由于您的 Struts 動作注意不到 Spring 的存在,所以您不需要重寫您的 Struts
代碼就可以使用其他控制反轉容器來替換掉 Spring。
DelegatingRequestProcessor
方法的確比第一種方法好,但是仍然存在一些問題。如果您使用一個不同的 RequestProcessor
,則需要手動整合 Spring 的 DelegatingRequestProcessor
。添加的代碼會造成維護的麻煩并且將來會降低您的應用程序的靈活性。此外,還有過一些使用一系列命令來代替 Struts RequestProcessor
的傳聞。 這種改變將會對這種解決方法的使用壽命造成負面的影響。
竅門 3. 將動作管理委托給 Spring
一個更好的解決方法是將 Strut 動作管理委托給 Spring。您可以通過在 struts-config
動作映射中注冊一個代理來實現。代理負責在 Spring 環境中查找 Struts 動作。由于動作在 Spring 的控制之下,所以它可以填充動作的 JavaBean 屬性,并為應用諸如 Spring 的 AOP 攔截器之類的特性帶來了可能。
清單 5 中的 Action
類與清單 4 中的相同。但是 struts-config 有一些不同:
清單 5. Spring 整合的委托方法
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config> <form-beans> <form-bean name="searchForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="isbn" type="java.lang.String"/> </form-bean> </form-beans>
<global-forwards type="org.apache.struts.action.ActionForward"> <forward name="welcome" path="/welcome.do"/> <forward name="searchEntry" path="/searchEntry.do"/> <forward name="searchSubmit" path="/searchSubmit.do"/> </global-forwards>
<action-mappings> <action path="/welcome" forward="/WEB-INF/pages/welcome.htm"/> <action path="/searchEntry" forward="/WEB-INF/pages/search.jsp"/> <action path="/searchSubmit" type="org.springframework.web.struts.DelegatingActionProxy" |(1) input="/searchEntry.do" validate="true" name="searchForm"> <forward name="success" path="/WEB-INF/pages/detail.jsp"/> <forward name="failure" path="/WEB-INF/pages/search.jsp"/> </action>
</action-mappings>
<message-resources parameter="ApplicationResources"/>
<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in>
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/beans.xml"/> </plug-in>
</struts-config>
|
清單 5 是一個典型的
struts-config.xml 文件,只有一個小小的差別。它注冊 Spring
代理類的名稱,而不是聲明動作的類名,如(1)處所示。DelegatingActionProxy 類使用動作映射名稱查找 Spring
環境中的動作。這就是我們使用 ContextLoaderPlugIn
聲明的環境。
將一個 Struts 動作注冊為一個 Spring bean 是非常直觀的,如清單 6 所示。我利用動作映射使用 <bean>
標記的名稱屬性(在這個例子中是 "/searchSubmit
")簡單地創建了一個 bean。這個動作的 JavaBean 屬性像任何 Spring bean 一樣被填充:
清單 6. 在 Spring 環境中注冊一個 Struts 動作
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit" class="ca.nexcel.books.actions.SearchSubmit"> <property name="bookService"> <ref bean="bookService"/> </property> </bean>
</beans>
|
動作委托的優點
動作委托解決方法是這三種方法中最好的。Struts 動作不了解 Spring,不對代碼作任何改變就可用于非 Spring 應用程序中。RequestProcessor
的改變不會影響它,并且它可以利用 Spring AOP 特性的優點。
動作委托的優點不止如此。一旦讓 Spring 控制您的 Struts 動作,您就可以使用 Spring 給動作補充更強的活力。例如,沒有 Spring 的話,所有的 Struts 動作都必須是線程安全的。如果您設置 <bean>
標記的 singleton
屬性為“false”,那么不管用何種方法,您的應用程序都將在每一個請求上有一個新生成的動作對象。您可能不需要這種特性,但是把它放在您的工具箱中也
很好。您也可以利用 Spring 的生命周期方法。例如,當實例化 Struts 動作時,<bean>
標記的 init-method 屬性被用于運行一個方法。類似地,在從容器中刪除 bean 之前,destroy-method 屬性執行一個方法。這些方法是管理昂貴對象的好辦法,它們以一種與 Servlet 生命周期相同的方式進行管理。
攔截 Struts
前
面提到過,通過將 Struts 動作委托給 Spring 框架而整合 Struts 和 Spring 的一個主要的優點是:您可以將
Spring 的 AOP 攔截器應用于您的 Struts 動作。通過將 Spring 攔截器應用于 Struts
動作,您可以用最小的代價處理橫切關注點。
雖然 Spring 提供很多內置攔截器,但是我將向您展示如何創建自己的攔截器并把它應用于一個 Struts 動作。為了使用攔截器,您需要做三件事:
- 創建攔截器。
- 注冊攔截器。
- 聲明在何處攔截代碼。
這看起來非常簡單的幾句話卻非常強大。例如,在清單 7 中,我為 Struts 動作創建了一個日志記錄攔截器。
這個攔截器在每個方法調用之前打印一句話:
清單 7. 一個簡單的日志記錄攔截器
package ca.nexcel.books.interceptors;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class LoggingInterceptor implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("logging before!"); } }
|
這個攔截器非常簡單。before()
方法在攔截點中每個方法之前運行。在本例中,它打印出一句話,其實它可以做您想做的任何事。下一步就是在 Spring 配置文件中注冊這個攔截器,如清單 8 所示:
清單 8. 在 Spring 配置文件中注冊攔截器
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <bean id="bookService" class="ca.nexcel.books.business.BookServiceImpl"/>
<bean name="/searchSubmit" class="ca.nexcel.books.actions.SearchSubmit"> <property name="bookService"> <ref bean="bookService"/> </property> </bean>
<!-- Interceptors --> <bean name="logger" class="ca.nexcel.books.interceptors.LoggingInterceptor"/> |(1)
<!-- AutoProxies --> <bean name="loggingAutoProxy" class="org.springframework.aop.framework.autoproxy. BeanNameAutoProxyCreator"> |(2) <property name="beanNames"> <value>/searchSubmit</valuesgt; |(3) </property> <property name="interceptorNames"> <list> <value>logger</value> |(4) </list> </property> </bean>
</beans>
|
您可能已經注意到了,清單 8 擴展了 清單 6 中所示的應用程序以包含一個攔截器。具體細節如下:
- 在 (1) 處,我注冊了這個攔截器。
- 在 (2) 處,我創建了一個 bean 名稱自動代理,它描述如何應用攔截器。還有其他的方法定義攔截點,但是這種方法常見而簡便。
- 在 (3) 處,我將 Struts 動作注冊為將被攔截的 bean。如果您想要攔截其他的 Struts 動作,則只需要在 "beanNames" 下面創建附加的
<value>
標記。 - 在 (4) 處,當攔截發生時,我執行了在 (1) 處創建的攔截器 bean 的名稱。這里列出的所有攔截器都應用于“beanNames”。
就是這樣。就像這個例子所展示的,將您的 Struts 動作置于 Spring 框架的控制之下,為處理您的 Struts 應用程序提供了一系列全新的選擇。在本例中,使用動作委托可以輕松地利用 Spring 攔截器提高 Struts 應用程序中的日志記錄能力。
結束語
在本文中,您已經學習了將 Struts 動作整合到 Spring 框架中的三種竅門。使用 Spring 的 ActionSupport
來整合 Struts(第一種竅門中就是這樣做的)簡單而快捷,但是會將 Struts 動作與 Spring 框架耦合在一起。如果您需要將應用程序移植到一個不同的框架,則需要重寫代碼。第二種解決方法通過委托 RequestProcessor
巧妙地解開代碼的耦合,但是它的可擴展性不強,并且當 Struts 的 RequestProcessor
變成一系列命令時,這種方法就持續不了很長時間。第三種方法是這三種方法中最好的:將 Struts 動作委托給 Spring 框架可以使代碼解耦,從而使您可以在您的 Struts 應用程序中利用 Spring 的特性(比如日志記錄攔截器)。
三種 Struts-Spring 整合竅門中的每一種都被實現成一個完整可用的應用程序。請參閱 下載 部分仔細研究它們。
下載
描述 | 名字 | 大小 | 下載方法 |
---|
ActionSupport sample code | j-sr2-actionsupport.zip | 5 MB |
FTP |
RequestProcessor sample code | j-sr2-requestprocessor.zip | 5 MB |
FTP |
Delegate sample code | j-sr2-delegate.zip | 5 MB |
FTP |