站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Struts 专题Struts源码研究 - Action-Input属性篇

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

摘要: Struts源码研究 - Action-Input属性篇 初学Struts,写了一个很简单的应用,主要功能和页面如下: 1、首页显示一个“添加新用户”的链接,点击该链接出发一个forward动作,页面...
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的配置文件中,首先将错误的配置文件列出如下:
  
  <?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="request"
  
  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() {
  return (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"/>
  <set-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了
  


↑返回目录
前一篇: Struts入门--从原理到安装及实例讲解
后一篇: Struts VS Spring 两种MVC框架比较