Struts 2框架本身大致可以分爲3個部分:核心控制器FilterDispatcher、業務控制器Action和用戶實現的企業業務邏輯組件。
3.1.1 核心控制器FilterDispatcher
核心控制器FilterDispatcher是Struts 2框架的基礎,包含了框架內部的控制流程和處理機制。業務控制器Action和業務邏輯組件是需要用戶來自己實現的。用戶在開發Action和業務邏輯組件的同時,還需要編寫相關的配置文件,供核心控制器FilterDispatcher來使用。
Struts 2的工作流程相對於Struts 1要簡單,與WebWork框架基本相同,所以說Struts 2是WebWork的升級版本。Struts 2框架按照模塊來劃分,可以分爲Servlet Filters、Struts核心模塊、攔截器和用戶實現部分。Struts 2框架結構圖如圖3.1所示。
圖3.1 Struts 2框架結構圖
一個請求在Struts 2框架中的處理大概分爲以下幾個步驟。
客戶端提交一個(HttpServletRequest)請求,如上文在瀏覽器中輸入http://localhost: 8080/bookcode/ch2/Reg.action就是提交一個(HttpServletRequest)請求。
請求被提交到一系列(主要是3層)的過濾器(Filter),如(ActionContextCleanUp、其他過濾器(SiteMesh等)、FilterDispatcher)。注意:這裏是有順序的,先ActionContext CleanUp,再其他過濾器(Othter Filters、SiteMesh等),最後到FilterDispatcher。
FilterDispatcher是控制器的核心,就是MVC的Struts 2實現中控制層(Controller)的核心。
FilterDispatcher詢問ActionMapper是否需要調用某個Action來處理這個(HttpServlet Request)請求,如果ActionMapper決定需要調用某個Action,FilterDispatcher則把請求的處理交給ActionProxy。
ActionProxy通過Configuration Manager(struts.xml)詢問框架的配置文件,找到需要調用的Action類。例如,用戶註冊示例將找到UserReg類。
ActionProxy創建一個ActionInvocation實例,同時ActionInvocation通過代理模式調用Action。但在調用之前,ActionInvocation會根據配置加載Action相關的所有Interceptor(攔截器)。
一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果result。
Struts 2的核心控制器是FilterDispatcher,有3個重要的方法:destroy()、doFilter()和Init(),可以在Struts 2的下載文件夾中找到源代碼,如代碼3.1所示。
代碼3.1 核心控制器FilterDispatcher
/**
* 定義一個Log實例
*/
private static final Log LOG = LogFactory.getLog(FilterDispatcher.class);
… ...
/**
* 存放屬性文件中的.STRUTS_I18N_ENCODING值
*/
private static String encoding;
/**
* 定義ActionMapper實例
*/
private static ActionMapper actionMapper;
/**
* 定義FilterConfig實例
*/
private FilterConfig filterConfig;
protected Dispatcher dispatcher;
/**
* 創建一個默認的dispatcher,初始化filter
* 設置默認的packages *
*/
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
dispatcher = createDispatcher(filterConfig);
dispatcher.init();
String param = filterConfig.getInitParameter("packages");
String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging";
if (param != null) {
packages = param + " " + packages;
}
this.pathPrefixes = parse(packages);
}
//銷燬filter方法
public void destroy() {
if (dispatcher == null) {
LOG.warn("something is seriously wrong, Dispatcher is not initialized (null) ");
} else {
dispatcher.cleanup();
}
}
/**
* 處理一個Action或者資源請求
* <p/>
* filter嘗試將請求同action mapping相匹配
* 如果找到,將執行dispatcher的serviceAction方法
* 如果Action處理失敗, doFilter將建立一個異常
* <p/>
* 如果請求靜態資源
* 資源將被直接複製給 response
* <p/>
* 如果找不到匹配Action 或者靜態資源,則直接跳出
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
ServletContext servletContext = getServletContext();
String timerKey = "FilterDispatcher_doFilter: ";
try {
UtilTimerStack.push(timerKey);
request = prepareDispatcherAndWrapRequest(request, response);
ActionMapping mapping;
try {
mapping=actionMapper.getMapping(request, dispatcher.getConfigurationManager());
} catch (Exception ex) {
LOG.error("error getting ActionMapping", ex);
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
return;
}
if (mapping == null) {
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
if (serveStatic && resourcePath.startsWith("/struts")) {
String name = resourcePath.substring("/struts".length());
findStaticResource(name, request, response);
} else {
//爲一個普通的request, 則通過
chain.doFilter(request, response);
}
return;
}
/**
*這個方法詢問ActionMapper是否需要調用某個Action來處理這個(request)請求,
*如果ActionMapper決定需要調用某個Action,
*FilterDispatcher則把請求的處理交給ActionProxy
dispatcher.serviceAction(request, response, servletContext, mapping);
} finally {
try {
ActionContextCleanUp.cleanUp(req);
} finally {
UtilTimerStack.pop(timerKey);
}
}
}
… …
}
在doFilter()方法中,將調用dispatcher.serviceAction,該方法如果找到相應的Action,將把用戶請求交給ActionProxy。serviceAction()代碼在Dispatcher.java中,如代碼3.2所示。
代碼3.2 Dispatcher類
...
/**
* 爲mapping加載類,並調用相應的方法或者直接返回result
* <p/>
* 根據用戶請求的參數,建立Action上下文
* 根據指定的Action’名稱和包空間名稱,加載一個Action代理 <tt>ActionProxy</tt>
* 然後Action的相應方法將被執行,
*/
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
//如果存在一個值棧,則建立一個新的並複製以備Action使用
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
if (stack!= null) {
extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
//FilterDispatcher把請求的處理交給ActionProxy
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, extraContext, true, false);
proxy.setMethod(method);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
//ActionMapping 直接返回一個result
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
if (stack != null) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
LOG.error("Could not find action or result", e);
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
throw new ServletException(e);
} finally {
UtilTimerStack.pop(timerKey);
}
}
…
}
從上面代碼中可以看出來,Struts 2用於處理用戶請求的Action實例,並不是用戶實現的業務控制器,而是Action代理。關於Action代理相關內容,讀者可以參考攔截器章節的介紹。
★ 提示 ★
前面一直在說Action可以是一個普通的Java類,與Servlet API完全分離,但是爲了實現業務邏輯,Action需要使用HttpServletRequest內容。
Struts 2設計的精巧之處就是使用了Action代理,Action代理可以根據系統的配置,加載一系列的攔截器,由攔截器將HttpServletRequest參數解析出來,傳入Action。同樣,Action處理的結果也是通過攔截器傳入HttpServletResponse,然後由HttpServletRequest傳給用戶。
其實,該處理過程是典型的AOP(面向切面編程)的方式,讀者可以在後面詳細瞭解到。Struts 2處理過程模型如圖3.2所示。
圖3.2 Struts 2處理過程模型
★ 說明 ★
攔截器是Struts 2框架的核心,通過攔截器,實現了AOP(面向切面編程)。使用攔截器,可以簡化Web開發中的某些應用,例如,權限攔截器可以簡化Web應用中的權限檢查。
3.1.2 業務控制器Action
業務控制器Action是由開發者自己編寫實現的,Action類可以是一個簡單的Java類,與Servlet API完全分離。Action一般都有一個execute()方法,也可以定義其他業務控制方法,詳細內容將在後面介紹。
Action的execute()返回一個String類型值,這與Struts 1返回的ActionForward相比,簡單易懂。Struts 2提供了一個ActionSupport工具類,該類實現了Action接口和validate()方法,一般開發者編寫Action可以直接繼承ActionSupport類。編寫Action類後,開發者還必須在配置文件中配置Action。一個Action的配置應該包含下面幾個元素:
該Action的name,即用戶請求所指向的URL。
Action所對應的class元素,對應Action類的位置。
指定result邏輯名稱和實際資源的定位。
Action是業務控制器,筆者建議在編寫Action的時候,儘量避免將業務邏輯放到其中,儘量減少Action與業務邏輯模塊或者組件的耦合程度。
3.1.3 業務模型組件
業務模型組件可以是實現業務邏輯的模塊,可以是EJB、POJO或者JavaBean,在實際開發中,對業務模型組件的區分和定義也是比較模糊的,實際上也超出了Struts 2框架的範圍。不同的開發者或者團隊,都有自己的方式來實現業務邏輯模塊,Struts 2框架的目的就是使用Action來調用業務邏輯模塊。例如一個銀行存款的業務邏輯模塊,如代碼3.3所示。
代碼3.3 模擬一個銀行業務的實現模塊
public class Bank {
//定義銀行賬戶
private String accounts;
//定義操作金額
private double money;
//屬性的getter和setter方法
public String getAccounts() {
return accounts;
}
public void setAccounts(String accounts) {
this.accounts = accounts;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
//模擬銀行存款方法
public boolean saving(String accounts, double money) {
//調用DAO等模塊讀寫數據庫
return dosomeing();
}
}
上面實例在實際開發中沒有任何意義,這裏只是作爲業務邏輯模塊來說明,在執行saving(String accounts,double money)方法時,可以調用相應的數據庫訪問其他組件,來實現存款操作。使用Action調用該業務邏輯組件可以在execute()方法中實現,如代碼3.4所示。
代碼3.4 業務控制器Bank_Saving_Action
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Bank_Saving_Action extends ActionSupport {
//定義銀行賬戶
private String accounts;
//定義操作金額
private double money;
public String execute() throws Exception {
//創建Bank實例
Bank bk=new Bank();
//調用存款方法
if (bk.saving(accounts, money)){
return SUCCESS;
}else{
return ERROR;
}
}
//屬性的getter和setter方法
public String getAccounts() {
return accounts;
}
public void setAccounts(String accounts) {
this.accounts = accounts;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
Bank_Saving_Action演示了對銀行存款業務邏輯組件的調用,這裏是通過在Action中創建業務邏輯組件實例的方式實現的。在實際開發中,可以使用靜態工廠獲得業務邏輯組件的實例或者使用IoC容器來管理。Action中不實現任何業務邏輯,只是負責組織調度業務邏輯組件。調用關係如圖3.3所示。
圖3.3 調用業務邏輯組件
★ 說明 ★
業務控制器Action一般情況下不是直接創建業務邏輯組件實例,而是使用工廠模式或者是從Spring容器中獲得業務邏輯組件實例,這樣可以提高系統的性能。
3.1.4 視圖組件
Struts 1只能支持JSP作爲視圖資源,而Struts 2的進步之處就是可以使用其他視圖技術,如FreeMarker、Velocity等。通過前面的學習和示例,讀者會知道Action的返回結果只是一個簡單的字符串,也就是一個邏輯上的視圖名稱,要與實際視圖資源對應,必須通過配置文件來實現。
在struts.xml配置文件中,每一個Aciton定義都有name和class屬性,同時還要指定result元素。result元素指定了邏輯視圖名稱和實際視圖的對應關係。每個result都有一個type屬性,前面介紹的struts.xml中並沒有顯式指定type值,即使用了默認的type類型:dispatcher,該結果類型支持JSP所謂視圖資源。
對於Struts 2的視圖技術和result返回類型,後面將詳細介紹。總結Strurs 2的框架工作流程,發現與WebWork基本相同,可以參考第1章關於WebWork框架的介紹和流程圖(如圖1.8所示)。