Struts源码研究 - Action-Input属性篇

初学Struts,写了一个很简单的应用,主要功能和页面如下:
1、首页显示一个“添加新用户”的链接,点击该链接出发一个forward动作,页面导向到添加用户的jsp页面
2、添加用户的jsp页面中,可供用户输入“用户名”和“用户描述”两项
3、用户输入完毕,将做输入数据合法性检查,检查通过,将输入信息保存进入文件(使用了Properties类),然后返回首页;检查失败返回添加用户页面
4、数据合法性检查分成两块,第一部分检查条件使用Struts的Validator,检查条件配置在Validator.xml中;第二部分检查放在ActionForm中,
检查失败将错误信息置入ActionErrors中,然后返回到添加用户的页面并显示错误信息。

JSP页面、ActionForm和Action类的代码书写都参照了struts-example应用,所以这里代码不再列举,请看附件中的代码包
这里值得一提的是,在开发过程中,碰到了一个小问题,正是由于该问题,才导致查看Struts源码,刨根问底的查找错误原因的过程
该错误发生在Struts的配置文件中,首先将错误的配置文件列出如下:
====================================================

 1<struts-config>
 2<!-- ======================================== Form Bean Definitions -->
 3<form-beans>
 4<form-bean name="CreateUserForm" type="com.zchome.CreateUserForm"></form-bean>
 5</form-beans>
 6<!-- ================================= Global Exception Definitions -->
 7<global-exceptions>
 8</global-exceptions>
 9<!-- =================================== Global Forward Definitions -->
10<global-forwards>
11<!-- Default forward to "Welcome" action -->
12<!-- Demonstrates using index.jsp to forward -->
13<forward name="welcome" path="/Welcome.do"></forward>
14</global-forwards>
15<!-- =================================== Action Mapping Definitions -->
16<action-mappings>
17<!-- Default "Welcome" action -->
18<!-- Forwards to Welcome.jsp -->
19<action parameter="/jsp/Welcome.jsp" path="/Welcome" type="org.apache.struts.actions.ForwardAction"></action>
20<action forward="/jsp/createuser.jsp" path="/createuserpage">
21</action>
22<action input="createuser" name="CreateUserForm" path="/docreateuser" scope="request" type="com.zchome.CreateUserAction">
23<forward name="createusersuccess" path="/jsp/Welcome.jsp"></forward>
24<forward name="createuser" path="/jsp/createuser.jsp"></forward>
25</action>
26</action-mappings>
27<!-- ===================================== Controller Configuration -->
28<controller>
29<set-property property="processorClass" value="org.apache.struts.tiles.TilesRequestProcessor"></set-property>
30</controller>
31<!-- ================================ Message Resources Definitions -->
32<message-resources parameter="resources.application"></message-resources>
33<!-- ======================================= Plug Ins Configuration -->
34<!-- ========== Tiles plugin =================== -->
35<!-- -->
36<!--   
37This plugin initialize Tiles definition factory. This later can takes some   
38parameters explained here after. The plugin first read parameters from web.xml, then   
39overload them with parameters defined here. All parameters are optional.   
40The plugin should be declared in each struts-config file.   
41\- definitions-config: (optional)   
42Specify configuration file names. There can be several comma   
43separated file names (default: ?? )   
44\- moduleAware: (optional - struts1.1)   
45Specify if the Tiles definition factory is module aware. If true (default),   
46there will be one factory for each Struts module.   
47If false, there will be one common factory for all module. In this later case,   
48it is still needed to declare one plugin per module. The factory will be   
49initialized with parameters found in the first initialized plugin (generally the   
50one associated with the default module).   
51true : One factory per module. (default)   
52false : one single shared factory for all modules   
53\- definitions-parser-validate: (optional)   
54Specify if xml parser should validate the Tiles configuration file.   
55true : validate. DTD should be specified in file header. (default)   
56false : no validation   
57  
58Paths found in Tiles definitions are relative to the main context.   
59\-->
60<!-- comment following if struts1.0.x -->
61<plug-in classname="org.apache.struts.tiles.TilesPlugin">
62<set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"></set-property>
63<set-property property="moduleAware" value="true"></set-property>
64<set-property property="definitions-parser-validate" value="true"></set-property>
65</plug-in>
66<!-- end comment if struts1.0.x -->
67<plug-in classname="org.apache.struts.validator.ValidatorPlugIn">
68<set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"></set-property>
69</plug-in>
70</struts-config>

====================================================
首先描述一下系统的出错背景:
1、从首页点击链接来到添加用户的页面 正常
2、在添加用户页面中输入Vlidator.xml文件中定义的错误数据,弹出Javascript对话框,提示出错 正常
3、在添加用户页面中输入合法数据,数据保存进入文件并重定向到首页 正常
4、在添加用户页面中输入ActionForm中定义的非法数据,系统应返回到添加用户的页面 出错!!!
OK,来着重看这个添加动作的定义,如下:
====================================================

1<action input="createuser" name="CreateUserForm" path="/docreateuser" scope="request" type="com.zchome.CreateUserAction">
2<forward name="createusersuccess" path="/jsp/Welcome.jsp"></forward>
3<forward name="createuser" path="/jsp/createuser.jsp"></forward>
4</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,里面有

  1<controller>这个标签,初步猜测ControllerConfig应该是读取这个标签的一个配置类   
  2<controller>这个标签应该定义了ActionServlet作为Controller的一些行为!   
  3OK,再来看ControllerConfig这个类中有关inputForward这个成员变量的一些代码,如下:   
  4====================================================   
  5/**   
  6* <p>Should the <code>input</code> property of {@link ActionConfig}   
  7* instances associated with this module be treated as the   
  8* name of a corresponding {@link ForwardConfig}. A <code>false</code>   
  9* value treats them as a module-relative path (consistent   
 10* with the hard coded behavior of earlier versions of Struts.</p>   
 11*   
 12* @since Struts 1.1   
 13*/   
 14protected boolean inputForward = false;   
 15  
 16public boolean getInputForward() {   
 17return (this.inputForward);   
 18}   
 19  
 20public void setInputForward(boolean inputForward) {   
 21this.inputForward = inputForward;   
 22}   
 23====================================================   
 24开始有点明白了,原来inputForward这个属性默认值是false,那么由于没有配置这个属性,那么上述的那个方法   
 25moduleConfig.getControllerConfig().getInputForward()自然就返回false了,Bingo   
 26那么重点就转移到了internalModuleRelativeForward这个方法了,看这个方法的源代码,如下:   
 27====================================================   
 28protected void internalModuleRelativeForward(   
 29String uri,   
 30HttpServletRequest request,   
 31HttpServletResponse response)   
 32throws IOException, ServletException {   
 33  
 34// Construct a request dispatcher for the specified path   
 35uri = moduleConfig.getPrefix() + uri;   
 36  
 37// Delegate the processing of this request   
 38// FIXME - exception handling?   
 39if (log.isDebugEnabled()) {   
 40log.debug(" Delegating via forward to '" + uri + "'");   
 41}   
 42doForward(uri, request, response);   
 43}   
 44  
 45protected void doForward(   
 46String uri,   
 47HttpServletRequest request,   
 48HttpServletResponse response)   
 49throws IOException, ServletException {   
 50  
 51// Unwrap the multipart request, if there is one.   
 52if (request instanceof MultipartRequestWrapper) {   
 53request = ((MultipartRequestWrapper) request).getRequest();   
 54}   
 55  
 56RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);   
 57if (rd == null) {   
 58response.sendError(   
 59HttpServletResponse.SC_INTERNAL_SERVER_ERROR,   
 60getInternal().getMessage("requestDispatcher", uri));   
 61return;   
 62}   
 63rd.forward(request, response);   
 64}   
 65====================================================   
 66从上可以看到,这个方法是将uri = moduleConfig.getPrefix() + uri;这个东东传给了doForward方法   
 67doForward这个方法又调用了javax.servlet.ServletContext的方法getRequestDispatcher这个方法   
 68既然出错信息中是路径出了问题,那么看来这个参数uri非常的重要,极有可能就是这个uri发生了错误导致了出错   
 69OK,开始剖析这个uri,从头开始看,这个uri是这样被赋值的:   
 70uri = moduleConfig.getPrefix() + uri   
 71  
 721moduleConfig.getPrefix()这个方法返回的应该是""   
 73(这个请看ActionServletInit方法,如果在web.xml文件中定义ActionServlet的时候,给定了一些init-params,那么这个prefix就有可能   
 74不为空,这里不再列举了)   
 752、代码右边的这个uri是从processValidate这个方法中定义的input,如下:   
 76String input = mapping.getInput();   
 77这个input应该是struts-config.xml文件中定义的那个actioninput,也就是“createuser”,如果Struts将其做了进一步的解析,那么这个   
 78input应该进一步被转化成为“/jsp/createuser.jsp   
 79  
 80好,到此为止,可以看到,这个uri不是“createuser”,那就是“/jsp/createuser.jsp”,再来看getRequestDispatcher这个方法的定义,   
 81翻开ServletAPI文档,可以看到如下一段话:   
 82====================================================   
 83public RequestDispatcher   
 84  
 85getRequestDispatcher(java.lang.String path)Returns a RequestDispatcher object that acts as a wrapper   
 86for the resource located at the given path. A RequestDispatcher object can be used to forward a request   
 87to the resource or to include the resource in a response. The resource can be dynamic or static.   
 88The pathname must begin with a "/" and is interpreted as relative to the current context root.   
 89Use getContext to obtain a RequestDispatcher for resources in foreign contexts. This method returns null   
 90if the ServletContext cannot return a RequestDispatcher.   
 91====================================================   
 92终于有拨云见日的感觉了,因为这段话和出错信息实在是太一致了!由上面这段话,我们可以断定,uri这个变量的值   
 93肯定是“createuser”,而不是我们所希望的“/jsp/createuser.jsp”。为什么会这样呢?显然是struts-config.xml中配置   
 94有些还是不对,或是缺了点什么。想到这里,很自然的就联想到上面所提到的InputForward这个配置项了,因为从字面意思上   
 95看来,这个配置项的用处就应该是将input的值解析成forward中对应的值,而且在ControllerConfig中,这个变量默认值是   
 96false,所以猜测将其改成true是不是就可以了呢?   
 97为了寻找答案,再次翻开struts-example(因为这个例子中的action也定义了input),终于找到了答案,和之前猜测的果然   
 98十分吻合,如下:   
 99====================================================   
100<controller>
101<set-property property="inputForward" value="true"></set-property>
102</controller>   
103====================================================   
104至此,问题解决,正确的action配置可以是如下两种:   
105====================================================   
1061、不使用inputForward   
107<action input="/jsp/createuser.jsp" name="CreateUserForm" path="/docreateuser" scope="request" type="com.zchome.CreateUserAction">
108<forward name="createusersuccess" path="/jsp/Welcome.jsp"></forward>
109</action>   
110  
1112、使用InputForward   
112<action input="createuser" name="CreateUserForm" path="/docreateuser" scope="request" type="com.zchome.CreateUserAction">
113<forward name="createusersuccess" path="/jsp/Welcome.jsp"></forward>
114<forward name="createuser" path="/jsp/createuser.jsp"></forward>
115</action>
116<controller>
117<set-property property="inputForward" value="true"></set-property>
118<set-property property="processorClass" value="org.apache.struts.tiles.TilesRequestProcessor"></set-property>
119</controller>   
120  
121====================================================   
122而且,从问题的定位过程中,还学到了一招,就是javax.servlet.RequestDispatcher   
123RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);   
124if (rd == null) {   
125response.sendError(   
126HttpServletResponse.SC_INTERNAL_SERVER_ERROR,   
127getInternal().getMessage("requestDispatcher", uri));   
128return;   
129}   
130rd.forward(request, response);   
131  
132以后再做页面重定向,只要给定相对的uri就可以了,再也不用写上一层的虚拟目录名或自己拼URL</controller></controller>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus