初學Struts,寫了一個很簡單的應用,主要功能和頁面如下:
1、首頁顯示一個“添加新用戶”的鏈接,點擊該鏈接出發一個forward動作,頁面導向到添加用戶的jsp頁面
2、添加用戶的jsp頁面中,可供用戶輸入“用戶名”和“用戶描述”兩項
3、用戶輸入完畢,將做輸入數據合法性檢查,檢查通過,將輸入信息保存進入文件(使用了Properties類),然後返回首頁;檢查失敗返回添加用戶頁面
4、數據合法性檢查分成兩塊,第一部分檢查條件使用Struts的Validator,檢查條件配置在Validator.xml中;第二部分檢查放在ActionForm中,檢查失敗將錯誤信息置入ActionErrors中,然後返回到添加用戶的頁面並顯示錯誤信息。
JSP頁面、ActionForm和Action類的代碼書寫都參照了struts-example應用,所以這裡代碼不再列舉,請看附件中的代碼包這裡值得一提的是,在開發過程中,碰到了一個小問題,正是由於該問題,才導致查看Struts源碼,刨根問底的查找錯誤原因的過程該錯誤發生在Struts的配置文件中,首先將錯誤的配置文件列出如下:
<?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 Bean Definitions -->
<form-beans>
<form-bean
name="CreateUserForm"
type="com.zchome.CreateUserForm"/>
</form-beans>
<!-- ================================= Global Exception Definitions -->
<global-exceptions>
</global-exceptions>
<!-- =================================== Global Forward Definitions -->
<global-forwards>
<!-- Default forward to "Welcome" action -->
<!-- Demonstrates using index.jsp to forward -->
<forward name="welcome" path="/Welcome.do"/>
</global-forwards>
<!-- =================================== Action Mapping Definitions -->
<action-mappings>
<!-- Default "Welcome" action -->
<!-- Forwards to Welcome.jsp -->
<action
path="/Welcome"
type="org.apache.struts.actions.ForwardAction"
parameter="/jsp/Welcome.jsp"/>
<action path="/createuserpage" forward="/jsp/createuser.jsp">
</action>
<action
path="/docreateuser"
type="com.zchome.CreateUserAction"
name="CreateUserForm"
scope="request"
input="createuser">
<forward name="createusersuccess" path="/jsp/Welcome.jsp"/>
<forward name="createuser" path="/jsp/createuser.jsp"/>
</action>
</action-mappings>
<!-- ===================================== Controller Configuration -->
<controller>
<set-property property="processorClass" value="org.apache.struts.tiles.TilesRequestProcessor"/>
</controller>
<!-- ================================ Message Resources Definitions -->
<message-resources parameter="resources.application"/>
<!-- ======================================= Plug Ins Configuration -->
<!-- ========== Tiles plugin =================== -->
<!-- -->
<!--
This plugin initialize Tiles definition factory. This later can takes some
parameters explained here after. The plugin first read parameters from web.xml, then
overload them with parameters defined here. All parameters are optional.
The plugin should be declared in each struts-config file.
- definitions-config: (optional)
Specify configuration file names. There can be several comma
separated file names (default: ?? )
- moduleAware: (optional - struts1.1)
Specify if the Tiles definition factory is module aware. If true (default),
there will be one factory for each Struts module.
If false, there will be one common factory for all module. In this later case,
it is still needed to declare one plugin per module. The factory will be
initialized with parameters found in the first initialized plugin (generally the
one associated with the default module).
true : One factory per module. (default)
false : one single shared factory for all modules
- definitions-parser-validate: (optional)
Specify if xml parser should validate the Tiles configuration file.
true : validate. DTD should be specified in file header. (default)
false : no validation
Paths found in Tiles definitions are relative to the main context.
-->
<!-- comment following if struts1.0.x -->
<plug-in className="org.apache.struts.tiles.TilesPlugin" >
<set-property property="definitions-config"
value="/WEB-INF/tiles-defs.xml" />
<set-property property="moduleAware" value="true" />
<set-property property="definitions-parser-validate" value="true" />
</plug-in>
<!-- end comment if struts1.0.x -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
</plug-in>
</struts-config>
首先描述一下系統的出錯背景:
1、從首頁點擊鏈接來到添加用戶的頁面 正常
2、在添加用戶頁面中輸入Vlidator.xml文件中定義的錯誤數據,彈出Javascript對話框,提示出錯 正常
3、在添加用戶頁面中輸入合法數據,數據保存進入文件並重定向到首頁 正常
4、在添加用戶頁面中輸入ActionForm中定義的非法數據,系統應返回到添加用戶的頁面 出錯!!!
OK,來著重看這個添加動作的定義,如下:
<action
path="/docreateuser"
type="com.zchome.CreateUserAction"
name="CreateUserForm"
scope="reques
t"
input="createuser">
<forward name="createusersuccess" path="/jsp/Welcome.jsp"/>
<forward name="createuser" path="/jsp/createuser.jsp"/>
</action>
從以上的定義可以看出,如果Validate驗證出錯,Struts應該為我們重定向到input域所定義的uri,即/jsp/createuser.jsp看起來應該沒有問題,再來看看出錯信息,如下:
java.lang.IllegalArgumentException: Path createuser does not start with a "/" character
at org.apache.catalina.core.ApplicationContext.getRequestDispatcher(ApplicationContext.java:1179)
at org.apache.catalina.core.ApplicationContextFacade.getRequestDispatcher(ApplicationContextFacade.java:174)
at org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1062)
at org.apache.struts.tiles.TilesRequestProcessor.doForward(TilesRequestProcessor.java:274)
at org.apache.struts.action.RequestProcessor.internalModuleRelativeForward(RequestProcessor.java:1012)
at org.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(TilesRequestProcessor.java:345)
at org.apache.struts.action.RequestProcessor.processValidate(RequestProcessor.java:980)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:255)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)
。。。以下省略。。。
出錯信息清楚的說明,“createuser”這個path應該以“/”字符開頭
為定位這個錯誤,從以上錯誤信息,開始打開Struts的源碼RequestProcessor.java進行研究,首先來到這一段:
public class RequestProcessor {
。。。。。。
protected boolean processValidate(HttpServletRequest request,
HttpServletResponse response,
ActionForm form,
ActionMapping mapping)
throws IOException, ServletException {
if (form == null) {
return (true);
}
// Was this request cancelled?
if (request.getAttribute(Globals.CANCEL_KEY) != null) {
if (log.isDebugEnabled()) {
log.debug(" Cancelled transaction, skipping validation");
}
return (true);
}
// Has validation been turned off for this mapping?
if (!mapping.getValidate()) {
return (true);
}
// Call the form bean's validation method
if (log.isDebugEnabled()) {
log.debug(" Validating input form properties");
}
ActionMessages errors = form.validate(mapping, request);
if ((errors == null) || errors.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace(" No errors detected, accepting input");
}
return (true);
}
// Special handling for multipart request
if (form.getMultipartRequestHandler() != null) {
if (log.isTraceEnabled()) {
log.trace(" Rolling back multipart request");
}
form.getMultipartRequestHandler().rollback();
}
// Has an input form been specified for this mapping?
String input = mapping.getInput();
if (input == null) {
if (log.isTraceEnabled()) {
log.trace(" Validation failed but no input form available");
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("noInput",
mapping.getPath()));
return (false);
}
// Save our error messages and return to the input form if possible
if (log.isDebugEnabled()) {
log.debug(" Validation failed, returning to '" + input + "'");
}
request.setAttribute(Globals.ERROR_KEY, errors);
if (moduleConfig.getControllerConfig().getInputForward()) {
ForwardConfig forward = mapping.findForward(input);
processForwardConfig( request, response, forward);
} else {
internalModuleRelativeForward(input, request, response);
}
return (false);
}
在出錯信息中,提到了internalModuleRelativeForward這個方法,所以著重看以上代碼的最後幾行,可以看到,如果moduleConfig.getControllerConfig().getInputForward()這個方法返回了false,那麼internalModuleRelativeForward這個方法將被調用。inputForward是什麼?ModuleConfig是管理所有配置信息的一個manager類,那麼moduleConfig.getControllerConfig()這個方法返回的肯定是ControllerConfig這個類的一個實例,那麼inputForward肯定是ControllerConfig類的一個成員變量了
再看看struts-config.xml,裡面有<controller>這個標簽,初步猜測ControllerConfig應該是讀取這個標簽的一個配置類,而<controller>這個標簽應該定義了ActionServlet作為Controller的一些行為!OK,再來看ControllerConfig這個類中有關inputForward這個成員變量的一些代碼,如下:
/**
* <p>Should the <code>input</code> property of {@link ActionConfig}
* instances associated with this module be treated as the
* name of a corresponding {@link ForwardConfig}. A <code>false</code>
* value treats them as a module-relative path (consistent
* with the hard coded behavior of earlier versions of Struts.</p>
*
* @since Struts 1.1
*/
protected boolean inputForward = false;
public boolean getInputForward() {
retur
n (this.inputForward);
}
public void setInputForward(boolean inputForward) {
this.inputForward = inputForward;
}
開始有點明白了,原來inputForward這個屬性默認值是false,那麼由於沒有配置這個屬性,那麼上述的那個方法moduleConfig.getControllerConfig().getInputForward()自然就返回false了,Bingo!
那麼重點就轉移到了internalModuleRelativeForward這個方法了,看這個方法的源代碼,如下:
protected void internalModuleRelativeForward(
String uri,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Construct a request dispatcher for the specified path
uri = moduleConfig.getPrefix() + uri;
// Delegate the processing of this request
// FIXME - exception handling?
if (log.isDebugEnabled()) {
log.debug(" Delegating via forward to '" + uri + "'");
}
doForward(uri, request, response);
}
protected void doForward(
String uri,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Unwrap the multipart request, if there is one.
if (request instanceof MultipartRequestWrapper) {
request = ((MultipartRequestWrapper) request).getRequest();
}
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
if (rd == null) {
response.sendError(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("requestDispatcher", uri));
return;
}
rd.forward(request, response);
}
從上可以看到,這個方法是將uri = moduleConfig.getPrefix() + uri;這個東東傳給了doForward方法,而doForward這個方法又調用了javax.servlet.ServletContext的方法getRequestDispatcher這個方法
既然出錯信息中是路徑出了問題,那麼看來這個參數uri非常的重要,極有可能就是這個uri發生了錯誤導致了出錯OK,開始剖析這個uri,從頭開始看,這個uri是這樣被賦值的:uri = moduleConfig.getPrefix() + uri
1、moduleConfig.getPrefix()這個方法返回的應該是""(這個請看ActionServlet的Init方法,如果在web.xml文件中定義ActionServlet的時候,給定了一些init-params,那麼這個prefix就有可能不為空,這裡不再列舉了)
2、代碼右邊的這個uri是從processValidate這個方法中定義的input,如下:
String input = mapping.getInput();這個input應該是struts-config.xml文件中定義的那個action的input,也就是“createuser”,如果Struts將其做了進一步的解析,那麼這個input應該進一步被轉化成為“/jsp/createuser.jsp”
好,到此為止,可以看到,這個uri不是“createuser”,那就是“/jsp/createuser.jsp”,再來看getRequestDispatcher這個方法的定義,翻開Servlet的API文檔,可以看到如下一段話:
public RequestDispatcher
getRequestDispatcher(java.lang.String path)Returns a RequestDispatcher object that acts as a wrapper
for the resource located at the given path. A RequestDispatcher object can be used to forward a request
to the resource or to include the resource in a response. The resource can be dynamic or static.
The pathname must begin with a "/" and is interpreted as relative to the current context root.
Use getContext to obtain a RequestDispatcher for resources in foreign contexts. This method returns null
if the ServletContext cannot return a RequestDispatcher.
終於有撥雲見日的感覺了,因為這段話和出錯信息實在是太一致了!由上面這段話,我們可以斷定,uri這個變量的值肯定是“createuser”,而不是我們所希望的“/jsp/createuser.jsp”。為什麼會這樣呢?顯然是struts-config.xml中配置有些還是不對,或是缺了點什麼。想到這裡,很自然的就聯想到上面所提到的InputForward這個配置項了,因為從字面意思上看來,這個配置項的用處就應該是將input的值解析成forward中對應的值,而且在ControllerConfig中,這個變量默認值是false,所以猜測將其改成true是不是就可以了呢?
為了尋找答案,再次翻開struts-example(因為這個例子中的action也定義了input),終於找到了答案,和之前猜測的果然十分吻合,如下:
<controller>
<set-property property="inputForward" value="true"/>
</controller>
至此,問題解決,正確的action配置可以是如下兩種:
1、不使用inputForward
<action
path="/docreateuser"
type="com.zchome.CreateUserAction"
name="CreateUserForm"
scope="request"
input="/jsp/createuser.jsp">
<forward name="createusersuccess" path="/jsp/Welcome.jsp"/>
</action>
2、使用InputForward
<action
path="/docreateuser"
type="com.zchome.CreateUserAction"
name="CreateUserForm"
scope="request"
input="createuser">
<forward name="createusersuccess" path="/jsp/Welcome.jsp"/>
<forward name="createuser" path="/jsp/createuser.jsp"/>
</action>
<controller>
<set-property property="inputForward" value="true"/>
<se
t-property property="processorClass" value="org.apache.struts.tiles.TilesRequestProcessor"/>
</controller>
====================================================
而且,從問題的定位過程中,還學到了一招,就是javax.servlet.RequestDispatcher:
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
if (rd == null) {
response.sendError(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
getInternal().getMessage("requestDispatcher", uri));
return;
}
rd.forward(request, response);
以後再做頁面重定向,只要給定相對的uri就可以了,再也不用寫上一層的虛擬目錄名或自己拼URL了