`

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框架入门学习---工程源码”提供了实践Struts框架的基础,涵盖了Action、MVC模式、配置文件、拦截器和视图等方面,对于初学者来说是一份宝贵的资料。通过分析和运行这个工程,你可以更好地理解...

    struts2--国际化入门

    总结起来,Struts2的国际化功能是通过资源文件、拦截器、Action和JSP标签协同工作实现的,它使得开发多语言支持的应用变得简单。通过理解和实践这些基础知识,你可以构建出适应全球用户的Web应用。

    Struts2--result配置的各种视图转发类型

    在Struts2中,结果(Result)是Action执行后控制流程的重要部分,它负责将处理后的数据或者控制逻辑转向合适的视图。这篇博文将深入探讨Struts2中的result配置以及各种视图转发类型。 首先,让我们理解Result的基本...

    Struts2+spring2+hibernate3实例源码-java源码

    ### Struts2 + Spring2 + Hibernate3 整合实例源码分析 #### 一、概述 随着企业级应用的发展,为了提高开发效率和系统维护性,越来越多的项目开始采用MVC设计模式。其中,Struts2作为MVC框架中的佼佼者,在前端...

    Struts2文件上传源码

    5. **错误处理与验证**: Struts2允许在Action中进行字段验证,如果上传过程中出现问题,可以通过`addFieldError`方法添加错误信息,然后返回错误的结果代码,如`INPUT`,让用户重新上传。 6. **源码分析**: 要深入...

    Struts中ActionError学习

    Struts是Java Web开发中的一款经典MVC框架,它的核心组件包括Action、ActionForm、Dispatcher Servlet(Controller)以及视图技术。在这个“Struts中ActionError学习”的主题中,我们将深入探讨ActionError这一机制...

    Struts2+上传文件源码

    这个"Struts2+上传文件源码"是一个演示如何在Struts2框架下实现文件上传的示例代码。 首先,我们来理解上传文件的基本流程。在Struts2中,文件上传是通过`Commons FileUpload`库来处理的,这是一个Apache提供的开源...

    Struts2+spring2+hibernate3实例源码-java源码[定义].pdf

    例如,`Login`和`Regist`两个Action分别对应登录和注册功能,它们的success和input结果分别指向成功或失败的页面。 - 同时,配置全局异常处理,将所有Action抛出的Exception异常映射到同一个结果页面,如`/error....

    struts2用户注册程序源码

    在Struts2中,配置文件(struts.xml)扮演着关键角色,它定义了请求和Action之间的映射关系。在用户注册程序中,我们需要在struts.xml中配置`UserRegisterAction`,指定其处理的请求路径以及返回的结果页面。例如: `...

    struts高级实战进阶-传智播客

    此外,标签是Struts提供的一个便利工具,它可以简化超链接的创建,自动处理URL重写和参数编码,使得开发者无需关心具体的Web应用程序路径和ActionServlet的映射。 防止表单重复提交是Web开发中的一个重要问题,...

    Struts1实现文件上传源码

    Struts1是一个经典的Java Web框架,它为开发者提供了一种结构化的MVC(Model-View-Controller)设计模式实现方式,极大地...通过研究这个例子,你可以更深入地理解Struts1的文件上传机制,并将其应用到自己的项目中。

    Struts的结构

    - **Action**:表示具体的业务逻辑操作,通常继承自`org.apache.struts.action.Action`类。 - **ActionForm**:用于封装客户端表单的数据,它通常与Action关联使用。 - **ActionMapping**:定义了请求URL和Action...

    struts2登录程序源码

    这个"struts2登录程序源码"是一个基础的教学资源,旨在帮助初学者理解如何在Struts2框架下实现用户登录功能。下面我们将深入探讨Struts2的核心概念以及在创建登录程序时涉及的关键知识点。 1. **Struts2框架简介**...

    struts2文件上传下载源代码

    例如,使用`&lt;constant&gt;`标签设置`struts.multipart.parser`为`jakarta`,以启用`Commons FileUpload`。 3. **编写Action类**:在Action类中,你需要创建一个字段来持有上传的文件,例如`private File file;`和对应...

    struts标签使用war

    实践中,你需要解压文件,导入到 IDE,运行并观察每个页面的效果,同时查阅源码了解标签背后的逻辑。 总之,熟练掌握 Struts 标签库能极大地提高开发效率,使你的 Java Web 应用程序更加规范和易于维护。通过实际...

    struts2.0基础配置-附加实例

    标签提到的“源码”意味着本文可能提供了Struts2 Action类或相关组件的源代码示例,帮助读者深入理解其实现原理。而“工具”可能指的是开发者在构建Struts2应用时可能用到的各种辅助工具,如IDE插件、构建工具(如...

    itcast Struts源码

    在本资料中,"itcast Struts源码"可能是指一个包含了Struts框架的示例项目,其中整合了Spring、Hibernate(SSH)这三大开源框架,用于演示如何在实际应用中协同工作。 1. **SSH整合**:Spring、Struts和Hibernate的...

    Struts2实现上传功能源码

    3. **配置Struts2 Action**:在Struts2的配置文件(struts.xml或@Action注解)中,定义一个接收文件上传的Action。例如: ```xml &lt;action name="uploadAction" class="com.example.UploadAction"&gt; ...

    struts文件的上传与下载源码

    1. **Struts配置**:首先,你需要在struts-config.xml文件中配置Action和ActionForm,声明处理文件上传和下载的类。例如,为文件上传设置一个`FileUploadAction`,并定义对应的`FileUploadForm`,其中包含用于存放...

    Struts2 常用标签使用(三十九)

    Struts2标签支持动态属性,如`&lt;s:textfield cssClass="inputText" /&gt;`,这使得开发者可以方便地控制HTML元素的样式和行为。 六、标签的国际化支持 通过`s:i18n`标签,Struts2可以轻松实现页面的国际化,比如`...

Global site tag (gtag.js) - Google Analytics