练习搭建和使用SSH框架的时候,遇到一个http方面很简单的问题。一个form的表单登录请求,表单为最基本的html表单内容,代码如下
在struts的action中需要获取输入的用户名和密码,最开始我采用的代码如下
但是在运行的时候,一直在user.setUserName(request.getAttribute("user_name").toString());这里报空指针异常,后来改成
user.setUserName(request.getParameter("user_name"));之后就可以正常运行了。
还是上面的SSH项目,为了了解这其中的区别,我将parameters和attributes都打印出来,结果如下
the actual type for request org.apache.struts2.dispatcher.StrutsRequestWrapper
attribute:< __cleanup_recursion_counter,1>
attribute:< struts.actionMapping,org.apache.struts2.dispatcher.mapper.ActionMapping@19a4e8e>
attribute:< struts.valueStack,com.opensymphony.xwork2.ognl.OgnlValueStack@19b7b9d>
parameter:<passwd,sdfg>
parameter:<user_name,lynn>
由此可见,表单传递请求的方式主要是通过parameters来完成。而attributes的使用就主要是框架内部使用。
虽然修改之后系统成功运行,但对于java的Http以及表单请求却产生了许多问题,借此机会做个了解。
以上是来自tomcat源代码中整理出来的Servlet系统的一部分接口、类的继承和实现关系。但由于这里使用的是HttpServletRequest,所以研究的重点要放在HttpServletRequest类上,下面是根据tomat源代码整理出来的HttpServletRequest系统的结构图。
从上图可以看出,本项目中的request.getAttribute和request.getParameter方法是继承自父接口ServletRequest的。这上面解释了tomcat所提供的http系统的大致框架结构。要理解这两个方法之间具体的差别,需要知道这两个对象的实际类型和对象实例化的点。使用
System.
out
.println(
"the actual type for request "
+request.getClass().getName());将request对象的类型打印出来,显示的结果为“
the actual type for request org.apache.struts2.dispatcher.StrutsRequestWrapper”
为什么是request的类型是org.apache.struts2.dispatcher.StrutsRequestWrapper呢?
StrutsRequestWrapper类是来自Struts框架,由于在项目中采用了SSH框架,Struts框架对tomcat的http系统进行了扩充。StrutsRequestWrapper是继承自HttpServletRequestWrapper类,
这个类又继承自ServletRequestWrapper,并且实现了HttpServletRequest接口
public
class
HttpServletRequestWrapper
extends
ServletRequestWrapper
implements
HttpServletRequest{/*....*/},它提供一个带HttpServletRequest类型参数的构造函数。代码如下
public
HttpServletRequestWrapper(HttpServletRequest request) {
super
(request);
}
其父类ServletRequestWrappet实现了ServletRequest接口,并且提供了一个带ServletRequest类型参数的构造函数,相关代码如下。
private
ServletRequest
request
;
/**
* Creates a ServletRequest adaptor wrapping the given request object.
*
*
@throws
java.lang.IllegalArgumentException
* if the request is null
*/
public
ServletRequestWrapper(ServletRequest request) {
if
(request ==
null
) {
throw
new
IllegalArgumentException(
"Request cannot be null"
);
}
this
.
request
= request;
}
在StrutsRequestWrapper类中重写了getAttribute方法,没有重写getParameter方法。其父类HttpServletRequestWrapper中对这两个方法都没有进行重写。所以,本项目中request对象调用的getParameter方法是来自于ServletRequestWrapper的,代码如下
@Override
public
String getParameter(String name) {
return
this
.
request
.getParameter(name);
}
结合其构造函数来看,具体其作用的还是在生成
StrutsRequestWrapper对象时传递进来的HttpServletRequest对象。
那就从Struts框架的启动配置入手,通过在web.xml中配置filter来将Struts框架引入项目。在web.xml中雨filter相关的配置代码如下。也就是说,tomcat在加载项目的时候,会以StrutsPrepareAndExecuteFilter作为入口,下面顺藤摸瓜。
<
filter
>
<
filter-name
>
struts
</
filter-name
>
<
filter-class
>
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
</
filter-class
>
</
filter
>
StrutsPrepareAndExecuteFilter实现了Filter接口,每次进行请求的时候,tomcat会调用其doFilter方法。在doFilter代码如下
public
void
doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws
IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try
{
prepare
.setEncodingAndLocale(request, response);
prepare
.createActionContext(request, response);
prepare
.assignDispatcherToThread();
if
(
excludedPatterns
!=
null
&&
prepare
.isUrlExcluded(request,
excludedPatterns
)) {
chain.doFilter(request, response);
}
else
{
request =
prepare
.wrapRequest(request);
ActionMapping mapping =
prepare
.findActionMapping(request, response,
true
);
if
(mapping ==
null
) {
boolean
handled =
execute
.executeStaticResourceRequest(request, response);
if
(!handled) {
chain.doFilter(request, response);
}
}
else
{
execute
.executeAction(request, response, mapping);
}
}
}
finally
{
prepare
.cleanupRequest(request);
}
}
其中先调用createActionContext创建了一个ActionContext对象。prePare是PrepareOpearation类型的对象,类的
createAction方法中部分代码如下
ActionContext oldContext = ActionContext.getContext();
if
(oldContext !=
null
) {
// detected existing context, so we are probably in a forward
ctx =
new
ActionContext(
new
HashMap<String, Object>(oldContext.getContextMap()));
}
else
{
ValueStack stack =
dispatcher
.getContainer().getInstance(ValueStackFactory.
class
).createValueStack();
stack.getContext().putAll(
dispatcher
.createContextMap(request, response,
null
,
servletContext
));
ctx =
new
ActionContext(stack.getContext());
}
request.setAttribute(
CLEANUP_RECURSION_COUNTER
, counter);
ActionContext. setContext(ctx);
return
ctx;
也就是说,这里生成的Map<String,Object> context对象来自旧的ActionContext对象。如果旧的不存在,那就用最初的配置文件构造一个出来,也就是servletContext对象,在PrepareOperations类的构造函数中被初始化,也就是根据tomcat调用StrutsPrepareAndExecuteFilter的init方法时,传递进来的配置内容产生。
然后parepare.wrapRequest方法实现对reques对象的包装,转成StrutsRequestWrapper对象。