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的配置文件中,首先将错误的配置文件列出如下:

====================================================

<?xmlversion="1.0"encoding="ISO-8859-1"?>

<!DOCTYPEstruts-configPUBLIC

"-//ApacheSoftwareFoundation//DTDStrutsConfiguration1.1//EN"

"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>

<!--========================================FormBeanDefinitions-->

<form-beans>

<form-bean

name="CreateUserForm"

type="com.zchome.CreateUserForm"/>

</form-beans>

<!--=================================GlobalExceptionDefinitions-->

<global-exceptions>

</global-exceptions>

<!--===================================GlobalForwardDefinitions-->

<global-forwards>

<!--Defaultforwardto"Welcome"action-->

<!--Demonstratesusingindex.jsptoforward-->

<forwardname="welcome"path="/Welcome.do"/>

</global-forwards>

<!--===================================ActionMappingDefinitions-->

<action-mappings>

<!--Default"Welcome"action-->

<!--ForwardstoWelcome.jsp-->

<action

path="/Welcome"

type="org.apache.struts.actions.ForwardAction"

parameter="/jsp/Welcome.jsp"/>

<actionpath="/createuserpage"forward="/jsp/createuser.jsp">

</action>

<action

path="/docreateuser"

type="com.zchome.CreateUserAction"

name="CreateUserForm"

scope="request"

input="createuser">

<forwardname="createusersuccess"path="/jsp/Welcome.jsp"/>

<forwardname="createuser"path="/jsp/createuser.jsp"/>

</action>

</action-mappings>

<!--=====================================ControllerConfiguration-->

<controller>

<set-propertyproperty="processorClass"value="org.apache.struts.tiles.TilesRequestProcessor"/>

</controller>

<!--================================MessageResourcesDefinitions-->

<message-resourcesparameter="resources.application"/>

<!--=======================================PlugInsConfiguration-->

<!--==========Tilesplugin===================-->

<!---->

<!--

ThisplugininitializeTilesdefinitionfactory.Thislatercantakessome

parametersexplainedhereafter.Thepluginfirstreadparametersfromweb.xml,then

overloadthemwithparametersdefinedhere.Allparametersareoptional.

Thepluginshouldbedeclaredineachstruts-configfile.

-definitions-config:(optional)

Specifyconfigurationfilenames.Therecanbeseveralcomma

separatedfilenames(default:??)

-moduleAware:(optional-struts1.1)

SpecifyiftheTilesdefinitionfactoryismoduleaware.Iftrue(default),

therewillbeonefactoryforeachStrutsmodule.

Iffalse,therewillbeonecommonfactoryforallmodule.Inthislatercase,

itisstillneededtodeclareonepluginpermodule.Thefactorywillbe

initializedwithparametersfoundinthefirstinitializedplugin(generallythe

oneassociatedwiththedefaultmodule).

true:Onefactorypermodule.(default)

false:onesinglesharedfactoryforallmodules

-definitions-parser-validate:(optional)

SpecifyifxmlparsershouldvalidatetheTilesconfigurationfile.

true:validate.DTDshouldbespecifiedinfileheader.(default)

false:novalidation

PathsfoundinTilesdefinitionsarerelativetothemaincontext.

-->

<!--commentfollowingifstruts1.0.x-->

<plug-inclassname="org.apache.struts.tiles.TilesPlugin">

<set-propertyproperty="definitions-config"

value="/WEB-INF/tiles-defs.xml"/>

<set-propertyproperty="moduleAware"value="true"/>

<set-propertyproperty="definitions-parser-validate"value="true"/>

</plug-in>

<!--endcommentifstruts1.0.x-->

<plug-inclassname="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">

<forwardname="createusersuccess"path="/jsp/Welcome.jsp"/>

<forwardname="createuser"path="/jsp/createuser.jsp"/>

</action>

====================================================

从以上的定义可以看出,如果Validate验证出错,Struts应该为我们重定向到input域所定义的uri,即/jsp/createuser.jsp

看起来应该没有问题,再来看看出错信息,如下:

====================================================

java.lang.IllegalArgumentException:Pathcreateuserdoesnotstartwitha"/"character

atorg.apache.catalina.core.ApplicationContext.getRequestDispatcher(ApplicationContext.java:1179)

atorg.apache.catalina.core.ApplicationContextFacade.getRequestDispatcher(ApplicationContextFacade.java:174)

atorg.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1062)

atorg.apache.struts.tiles.TilesRequestProcessor.doForward(TilesRequestProcessor.java:274)

atorg.apache.struts.action.RequestProcessor.internalModuleRelativeForward(RequestProcessor.java:1012)

atorg.apache.struts.tiles.TilesRequestProcessor.internalModuleRelativeForward(TilesRequestProcessor.java:345)

atorg.apache.struts.action.RequestProcessor.processValidate(RequestProcessor.java:980)

atorg.apache.struts.action.RequestProcessor.process(RequestProcessor.java:255)

atorg.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)

atorg.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)

。。。以下省略。。。

====================================================

出错信息清楚的说明,“createuser”这个path应该以“/”字符开头

为定位这个错误,从以上错误信息,开始打开Struts的源码RequestProcessor.java进行研究,首先来到这一段:

====================================================

publicclassRequestProcessor{

。。。。。。

protectedbooleanprocessValidate(HttpServletRequestrequest,

HttpServletResponseresponse,

ActionFormform,

ActionMappingmapping)

throwsIOException,ServletException{

if(form==null){

return(true);

}

//Wasthisrequestcancelled?

if(request.getAttribute(Globals.CANCEL_KEY)!=null){

if(log.isDebugEnabled()){

log.debug("Cancelledtransaction,skippingvalidation");

}

return(true);

}

//Hasvalidationbeenturnedoffforthismapping?

if(!mapping.getValidate()){

return(true);

}

//Calltheformbean'svalidationmethod

if(log.isDebugEnabled()){

log.debug("Validatinginputformproperties");

}

ActionMessageserrors=form.validate(mapping,request);

if((errors==null)||errors.isEmpty()){

if(log.isTraceEnabled()){

log.trace("Noerrorsdetected,acceptinginput");

}

return(true);

}

//Specialhandlingformultipartrequest

if(form.getMultipartRequestHandler()!=null){

if(log.isTraceEnabled()){

log.trace("Rollingbackmultipartrequest");

}

form.getMultipartRequestHandler().rollback();

}

//Hasaninputformbeenspecifiedforthismapping?

Stringinput=mapping.getInput();

if(input==null){

if(log.isTraceEnabled()){

log.trace("Validationfailedbutnoinputformavailable");

}

response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,

getInternal().getMessage("noInput",

mapping.getPath()));

return(false);

}

//Saveourerrormessagesandreturntotheinputformifpossible

if(log.isDebugEnabled()){

log.debug("Validationfailed,returningto'"+input+"'");

}

request.setAttribute(Globals.ERROR_KEY,errors);

if(moduleConfig.getControllerConfig().getInputForward()){

ForwardConfigforward=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>Shouldthe<code>input</code>propertyof{@linkActionConfig}

*instancesassociatedwiththismodulebetreatedasthe

*nameofacorresponding{@linkForwardConfig}.A<code>false</code>

*valuetreatsthemasamodule-relativepath(consistent

*withthehardcodedbehaviorofearlierversionsofStruts.</p>

*

*@sinceStruts1.1

*/

protectedbooleaninputForward=false;

publicbooleangetInputForward(){

return(this.inputForward);

}

publicvoidsetInputForward(booleaninputForward){

this.inputForward=inputForward;

}

====================================================

开始有点明白了,原来inputForward这个属性默认值是false,那么由于没有配置这个属性,那么上述的那个方法

moduleConfig.getControllerConfig().getInputForward()自然就返回false了,Bingo!

那么重点就转移到了internalModuleRelativeForward这个方法了,看这个方法的源代码,如下:

====================================================

protectedvoidinternalModuleRelativeForward(

Stringuri,

HttpServletRequestrequest,

HttpServletResponseresponse)

throwsIOException,ServletException{

//Constructarequestdispatcherforthespecifiedpath

uri=moduleConfig.getPrefix()+uri;

//Delegatetheprocessingofthisrequest

//FIXME-exceptionhandling?

if(log.isDebugEnabled()){

log.debug("Delegatingviaforwardto'"+uri+"'");

}

doForward(uri,request,response);

}

protectedvoiddoForward(

Stringuri,

HttpServletRequestrequest,

HttpServletResponseresponse)

throwsIOException,ServletException{

//Unwrapthemultipartrequest,ifthereisone.

if(requestinstanceofMultipartRequestWrapper){

request=((MultipartRequestWrapper)request).getRequest();

}

RequestDispatcherrd=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,如下:

Stringinput=mapping.getInput();

这个input应该是struts-config.xml文件中定义的那个action的input,也就是“createuser”,如果Struts将其做了进一步的解析,那么这个

input应该进一步被转化成为“/jsp/createuser.jsp”

好,到此为止,可以看到,这个uri不是“createuser”,那就是“/jsp/createuser.jsp”,再来看getRequestDispatcher这个方法的定义,

翻开Servlet的API文档,可以看到如下一段话:

====================================================

publicRequestDispatcher

getRequestDispatcher(java.lang.Stringpath)ReturnsaRequestDispatcherobjectthatactsasawrapper

fortheresourcelocatedatthegivenpath.ARequestDispatcherobjectcanbeusedtoforwardarequest

totheresourceortoincludetheresourceinaresponse.Theresourcecanbedynamicorstatic.

Thepathnamemustbeginwitha"/"andisinterpretedasrelativetothecurrentcontextroot.

UsegetContexttoobtainaRequestDispatcherforresourcesinforeigncontexts.Thismethodreturnsnull

iftheServletContextcannotreturnaRequestDispatcher.

====================================================

终于有拨云见日的感觉了,因为这段话和出错信息实在是太一致了!由上面这段话,我们可以断定,uri这个变量的值

肯定是“createuser”,而不是我们所希望的“/jsp/createuser.jsp”。为什么会这样呢?显然是struts-config.xml中配置

有些还是不对,或是缺了点什么。想到这里,很自然的就联想到上面所提到的InputForward这个配置项了,因为从字面意思上

看来,这个配置项的用处就应该是将input的值解析成forward中对应的值,而且在ControllerConfig中,这个变量默认值是

false,所以猜测将其改成true是不是就可以了呢?

为了寻找答案,再次翻开struts-example(因为这个例子中的action也定义了input),终于找到了答案,和之前猜测的果然

十分吻合,如下:

====================================================

<controller>

<set-propertyproperty="inputForward"value="true"/>

</controller>

====================================================

至此,问题解决,正确的action配置可以是如下两种:

====================================================

1、不使用inputForward

<action

path="/docreateuser"

type="com.zchome.CreateUserAction"

name="CreateUserForm"

scope="request"

input="/jsp/createuser.jsp">

<forwardname="createusersuccess"path="/jsp/Welcome.jsp"/>

</action>

2、使用InputForward

<action

path="/docreateuser"

type="com.zchome.CreateUserAction"

name="CreateUserForm"

scope="request"

input="createuser">

<forwardname="createusersuccess"path="/jsp/Welcome.jsp"/>

<forwardname="createuser"path="/jsp/createuser.jsp"/>

</action>

<controller>

<set-propertyproperty="inputForward"value="true"/>

<set-propertyproperty="processorClass"value="org.apache.struts.tiles.TilesRequestProcessor"/>

</controller>

====================================================

而且,从问题的定位过程中,还学到了一招,就是javax.servlet.RequestDispatcher:

RequestDispatcherrd=getServletContext().getRequestDispatcher(uri);

if(rd==null){

response.sendError(

HttpServletResponse.SC_INTERNAL_SERVER_ERROR,

getInternal().getMessage("requestDispatcher",uri));

return;

}

rd.forward(request,response);

以后再做页面重定向,只要给定相对的uri就可以了,再也不用写上一层的虚拟目录名或自己拼URL了。

相关推荐