首先,我先大概介紹一下jpetstore的整體架構,spring的這個版本主要使用了struts+spring+ibatis的框架組合,
而在MVC層的框架,這個版本又同時提供了兩個實現版本,一個是struts,一個是spring 自帶的web框架,

而數據庫持久層使用的是ibatis框架,這個框架是一個SQL映射框架,輕量級而且使用非常容易,基本上會

使用JDBC的朋友看一兩個小時就會使用了。
下圖是該應用的一個簡略架構圖,沒有什么好的工具,就大概畫了一個,雖然比較簡單,不過也基本可以

概括應用的整體框架了,首先是JSP請求,通過struts-config.xml(這里只是根據struts來畫的,spring其實也差不多),

請求轉到相應的Action中,可以看到,這里有一個BaseAction,是所有Action實現的父類,這個類的作用稍后再講,

然后就是每個Action通過PetStoreFacade的對象調用后臺DAO對象,從而通過ibatis進行數據的持久操作,

這里使用了門面(Facade)模式,隔離了前臺與后臺耦合度,而PetStoreFacade就是這個門面。結合下圖,

相信大家對整個jpetstore會有個大概的了解。

 

 

好了,大概的結構講了下,接下來我們就從代碼入手,在這里考慮到struts大家比較熟悉,因此,

本文是以struts版本來講,同時聲明,本文并不會一段段代碼詳細講述,而只是提供一個學習的參考,

大概講解一下流程,讓大家不再茫然不知從哪開始,好了,廢話也不多說了。

既然是WEB應用,那當然首先從配置文件看起,那就非web.xml莫屬了,打開WEB-INF目錄下的web.xml,

我們挑出目前我們應該關注的配置代碼:
代碼  
<servlet>    
   <servlet-name>petstore</servlet-name>    
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
   <load-on-startup>2</load-on-startup>    
</servlet>    
   
<servlet>    
   <servlet-name>action</servlet-name>    
   <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>    
   <load-on-startup>3</load-on-startup>    
</servlet>    
   
<servlet-mapping>    
   <servlet-name>petstore</servlet-name>    
   <!--   
   <servlet-name>action</servlet-name>   
   -->    
   <url-pattern>*.do</url-pattern>    
</servlet-mapping>
在這里可看到兩個servlet設置,以及一個servlet mapping,第一個petstore的servlet是用于spring web框架的,

而第二個action的servlet就是用于struts的,而這個版本的mapping默認的是使用spring web,可以看到,<servlet-name>action</servlet-name>這一行是被注釋掉了,我們要使用struts的話,那就把這個注釋去掉,

改為注釋掉<servlet-name>petstore</servlet-name>,好了,如此注釋以后,整個應用就會以struts的框架運行。

(這些配置是什么意思?如果你還搞不懂的話,建議你先去學學基礎再來研究應用吧)

接下來我們可以打開strutc-config.xml文件,這里就是struts的默認配置文件,在這里可以看到struts的相關配置信息。

好了,接下來我們就以一個請求來講述基本的請求流程,以search為例,生成項目,再啟動,

成功之后進入應用的首頁,我們終于可以看到久違的鸚鵡界面了,激動吧,呵呵,在界面的右上角,

有一個Search文本框,我們就以這個Search為例子來講解,輸入一個關鍵字cat,然后點search,

結果出來了,這個過程的內部是如何運作的呢?

我們用鼠標右鍵點擊頁面,然后選擇屬性,我們看到顯示的地址可能是:
http://localhost:8080/shop/searchProducts.do;jsessionid=E2D01E327B82D068FEE9D073CA33A2A3

這個地址就是我們剛才點擊查詢時提交的地址了,我們看到searchProducts.do這個字符串,

我們之前在web.xml里面的設置大家還記得嗎?
<servlet-mapping>
   <!--
   <servlet-name>petstore</servlet-name>
   -->
   <servlet-name>action</servlet-name>
   <url-pattern>*.do</url-pattern>
</servlet-mapping>
代表著所有以.do為結尾的請求,都將被名叫action的servlet處理,也就是通過struts-config配置進行處理,那我們就去struts-config.xml里面看看,打開struts-config.xml文件,ctrl+F彈出查詢界面,輸入searchProducts,我們就可查到
<action path="/shop/searchProducts" type="org.springframework.samples.jpetstore.web.
struts.SearchProductsAction" name="emptyForm" scope="session" validate="false">    
<forward name="success" path="/WEB-INF/jsp/struts/SearchProducts.jsp"/>    
</action>
根據以上配置,我們可以得知,剛才我們的提交將會被SearchProductsAction處理,而該action的form是emptyForm,查找emptyForm這個別名,我們可以找到它指向一個BaseActionForm,打開這個form我們可以看到,里面只有兩個validate方法和一個addErrorIfStringEmpty方法,沒有任何的屬性,那就是說search這個提交并沒有把我們輸入的關鍵字保存在form里面,打開SearchProductsAction,我們看到execute方法里的第一句就是

String keyword = request.getParameter("keyword");
也就是說我們輸入的關鍵字是以參數的形式傳入到request里面,參數名字為“keyword”,我們打開IncludeTop.jsp,這個文件在WEB-INF/jsp/struts目錄下。

注意了,jsp目錄下分別有spring以及struts兩個目錄,這兩個目錄就是分別對應兩個web框架的,我們使用的是struts所以jsp代碼就到struts目錄里面,為什么打開IncludeTop.jsp呢,我們可以看到,無論我們進入哪個頁面,search那個文本框都存在,也就是說,這個文本框是被當作一個模板插入到所有頁面當中去的,我們隨便打開一個頁面,就可以看到頁面開頭都是:

<%@ include file="IncludeTop.jsp" %>
這句就是把IncludeTop.jsp當作頁面頭部包含進來,所以凡是包含這個頭頁面的頁面,他們的頭部都是一樣的,這也是我們在開發中常用的一種方式,統一界面的一種方式,我們也可以看到在這些文件尾部也有類似的代碼,如:

<%@ include file="IncludeBottom.jsp" %>

其作用也是一樣。打開IncludeTop.jsp后,我們看到其中有一段代碼:

<form action="<c:url value="/shop/searchProducts.do"/>" method="post">
<input type="hidden" name="search" value="true"/>
<input name="keyword" size="14"/> <input border="0" src="../images/search.gif"

type="image" name="search"/>
</form>
這段代碼就是我們search文本框以及提交鏈接的代碼,在這里就不做詳細介紹了。

好了,接下來我們再看看這個action的后續代碼

if (keyword != null) {  
if (!StringUtils.hasLength(keyword)) {  
   request.setAttribute("message", "Please enter a keyword to search for,

then press the search button.");  
   return mapping.findForward("failure");  
   }  
PagedListHolder productList = new PagedListHolder(getPetStore().

searchProductList(keyword.toLowerCase()));  
productList.setPageSize(4);  
request.getSession().setAttribute("SearchProductsAction_productList", productList);  
request.setAttribute("productList", productList);  
return mapping.findForward("success");  
}

這里第一句就是判斷keyword是否為空,不為空就執行其中的代碼,我們search的時候這個keyword肯定是不為空的,

那就是說這段if包含的代碼就是我們search的處理代碼了,有的人也許會說,如果我不輸入關鍵字而直接點search呢,

這keyword不就是為空了嗎?我想這個你在此處加個斷點,再運行一下就知道了,雖然你沒有輸入,

但是keyword一樣不是null,它將是一個空字符串,而不是空對象。我們看到if里面還包含有一個if,

這里就是判斷keyword是否為空字符串了,StringUtils.hasLength()方式是一個工具類,

用來判斷keyword是否為空字符串,如果是空字符串就返回false,而這句判斷當返回false時,

因為前面有個感嘆號,所以值為false就執行被if所包含的語句,里面的代碼就是保存一個錯誤信息,然后return mapping.findForward("failure");,這句的意思就不再解釋了。

現在假設我們正確輸入keyword,那么程序將不會執行if語句中的代碼,直接向下執行,我們看到


PagedListHolder productList = new PagedListHolder(getPetStore().

searchProductList(keyword.toLowerCase()));

這句代碼,PagedListHolder是spring提供的一個用來保存查詢結構類,通常用來進行分頁處理,

因此我們可以知道

getPetStore().searchProductList(keyword.toLowerCase())
這一句就是用來查詢,并返回查詢結果的。getPetStore()這個方法是繼承自BaseAction的,它將獲得一個PetStoreFacade的實現,我們打開BaseAction的代碼,可以看到如下代碼

public void setServlet(ActionServlet actionServlet) {  
       super.setServlet(actionServlet);  
       if (actionServlet != null) {  
           ServletContext servletContext = actionServlet.getServletContext();  
       WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);  
       this.petStore = (PetStoreFacade) wac.getBean("petStore");  
       }  
}

這句代碼里面最重要的一句就是

WebApplicationContext wac = WebApplicationContextUtils.

getRequiredWebApplicationContext(servletContext);
這句代碼的作用就是獲取一個WebApplicationContext對象wac,在這里我們只需要知道這個對象的作用,而不對其進行深入研究,通過這個對象,我們可以根據spring的配置文件,獲得相應的Bean,下面這句就是用來獲取相應bean的語句:


this.petStore = (PetStoreFacade) wac.getBean("petStore");
petStore這個就是bean的id,這個petStore的bean具體是哪個類呢?我們打開applicationContext.xml,可以找到以下配置代碼


<bean id="petStore" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
   <property name="accountDao" ref="accountDao"/>
   <property name="categoryDao" ref="categoryDao"/>
   <property name="productDao" ref="productDao"/>
   <property name="itemDao" ref="itemDao"/>
   <property name="orderDao" ref="orderDao"/>
</bean>
從這里可以看到,petStoreFacade的具體類就是PetStoreImpl,而這個類當中,分別通過spring IOC注入了幾個DAO的bean,這幾個DAO的配置可以在dataAccessContext-local.xml文件里面找到,我們打開PetStoreImpl這個實現類,我們看到類里有一個searchProductList方法,這個方法就是我們在Action當中調用的方法

return this.productDao.searchProductList(keywords);
從這句代碼可以看出,這個方法是通過調用productDao的searchProductList方法來獲得結果的,

productDao這個DAO從上面的配置文件可以看出,是通過IOC容器進行注入的,我們打開dataAccessContext-local.xml文件,可以看到

<bean id="productDao" class="org.springframework.samples.jpetstore.dao.ibatis

.SqlMapProductDao">
   <property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
這句配置代表了productDao的實現類就是SqlMapProductDao,同時這個類包含有一個sqlMapClient的屬性對象,這個對象也是通過ioc注入的,再在這個配置文件里,我們可以找到如下一段代碼

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
   <property name="configLocation" value="WEB-INF/sql-map-config.xml"/>
   <property name="dataSource" ref="dataSource"/>
</bean>
這段代碼就是sqlMapClient這個bean的配置,這里的實現是SqlMapClientFactoryBean,它是spring專門為ibatis框架提供的一個支持,通過這個對象就可以很好的集成ibatis框架,具體的介紹可以通過spring官方文檔或者是其他一些教程獲得,在這里就不多做介紹。

好了,接下來我們知道了,實際查詢數據是通過DAO的實現類SqlMapProductDao進行的,而SqlMapProductDao當中就是通過了ibatis進行數據的查詢,從而返回結果,這里也就不多做介紹了,大家可以通過ibatis的教程獲得ibatis的使用方法,非常的簡單,search操作從前臺到后臺的大概流程就介紹到這里了。

在這個參考文章中,我并沒有對具體技術做過多的講解,那是因為本文只是作為一個研究jpetstore的參考,提供一個可供參考的研究流程,主要是為了那些想開始研究jpetstore但是又不知道從哪開始或者是不知道如何進行研究的新人朋友們而準備的,如果具體的講解每一部分,那我想將不僅僅是一篇文章就可以完成的事情,因為這里涉及到struts,spring,ibatis等具體的框架技術,每一個框架基本都可以寫成一本書,用一篇文章來講就不太實際了,而且我個人更傾向于遇到不理解的地方的時候,多使用google來搜索,這樣能夠進一步加深自己對問題的理解。

好了,關于jpetstore源碼研究入門的文章就寫到這里結束了,由于本人技術和文筆有限,有錯漏或者表達不當的地方請不要介意,歡迎各位朋友來指正。



開心過好每一天。。。。。