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了。