您的位置:首页 > 编程语言 > Java开发

Struts2 源码剖析 控制部分-----1

2015-11-03 16:51 495 查看
这部分着重分析从我们发出一个uri请求,一直到代码运行到我们自己写的action类为止,struts的控制部分的代码(还有数据流部分,我们后面再分析)

已经用了快1年多的struts2了,一直认为对开源框架的学习,应该到源码级别才行,否则开源岂不是没有意义了。

struts的运行图如下:



    1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

  2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

  3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;

  4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

  5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

  6、ActionProxy创建一个ActionInvocation的实例。

  7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

  8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

先看我自己画的时序图:



struts在web.xml中的配置如下:
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
很显然,我们得去StrutsPrepareAndExecuteFilter里看看,这是一个filter。

现在,我们先不看代码,根据以往关于filter的学习经历,我们能知道,filter至少有3个方法:

init(FilterConfig filterConfig)

doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

destroy()

init方法是初始化的,只运行一次,destroy一样,也只运行一次。

doFilter是真正的struts的业务逻辑。

我们猜一下,init应该就是struts的环境初始化,目前我们先不管它。(下一节,我们再探讨init方法)

我们就按照前面说的struts运行的八个步骤来看看doFilter。

 
//StrutsPrepareAndExecuteFilter.java
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
//之前的代码 我们先不管
//第三步 找到action的信息
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);
}
}
上面的ActionMapping mapping = prepare.findActionMapping(request, response, true);就是找到对应的action,怎么找的?看代码:
//PrepareOperations.java
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} catch (Exception ex) {
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}

return mapping;
}
这里有一个actionmapping,它是干什么的?

看代码:
public class ActionMapping {

private String name;
private String namespace;
private String method;
private String extension;
private Map<String, Object> params;
private Result result;
//.....
}
懂了么?,就是对应一个action

 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());

上面的这行,就是先获取ActionMapper,再通过ActionMapper获取ActionMapping。

actionMapper是干什么的?

暂时不清楚

但是我能知道,getInstance返回的是ActionMapper的实现类:DefaultActionMapper

   
//DefaultActionMapper.java 下面的就是时序图中的第四步
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
//去除工程名等等 获得uri
String uri = RequestUtils.getUri(request);

//url的带; jsessionid 问题
int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

//删除扩展名,如.action或者.do
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}

//把uri中的信息分析到mapping中
parseNameAndNamespace(uri, mapping, configManager);
//处理特殊参数
handleSpecialParameters(request, mapping);
//主要是用来处理形如/userAction!getAll.action的请求,分离action名和方法名:
return parseActionName(mapping);
}
对DefaultActionMapper的getMapping方法,我们得边调试边看。

例如我访问下面这个路径:
http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/login.jsp
RequestUtils.getUri(request)返回的string是:/login.jsp

另外,经过uri = dropExtension(uri, mapping)后,uri就是null了。

然后就直接返回一个null

我们换一个请求路径
http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/user/login.action
此时RequestUtils.getUri(request)的返回值就是:/user/login.action

经过dropExtension(uri, mapping)后,uri就是:/user/login

我们看到parseNameAndNamespace,这里干的就是从uri中分离得到请求的action名、命名空间。

这里面的代码,比较复杂,我们只需要知道它干了什么事就OK,它不是我们的重点。

之后的parseActionName干的就是处理动态方法调用的事,感兴趣的,大家可以看看代码

之后,我们就回到了StrutsPrepareAndExecuteFilter的dofilter方法,此时调用的是:

 execute.executeAction(request, response, mapping); (时序图中的第五步)

 查看代码,最终我们到达了Dispatcher的serviceAction方法:

 serviceAction方法,如下,我已经删掉了一些数据相关的代码,例如valuestack
//Dispatcher.java  时序图中的第六步
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {

//处理栈的问题

String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();

Configuration config = configurationManager.getConfiguration();
//时序图中的第7,8步
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);

//时序图中的第9步
//4、如果ActionMapper决定需要调用某个Action,
//FilterDispatcher把请求的处理交给ActionProxy;
proxy.execute();
}

  

 

//StrutsActionProxy.java  时序图中的第10步
public String execute() throws Exception {
ActionContext previous = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
try {
return invocation.invoke();
} finally {
if (cleanupContext)
ActionContext.setContext(previous);
}
}


StrutsActionProxy.execute中的invocation是DefaultActionInvocation的实例,其invoke方法如下:

 
//DefaultActionInvocation.java
public String invoke() throws Exception {

if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
} else {
resultCode = invokeActionOnly();
}
return resultCode;
}

}
关于interceptor.getInterceptor().intercept(DefaultActionInvocation.this),这是一个典型的责任链模式,如果对它不清楚,大家可以参考
说说struts2中拦截器的请求流程一(模拟大致流程)

DefaultActionInvocation中有很多过滤器,对每一个请求,都是首先经过那个一个个过滤器然后进入action,之后再通过过滤器

invokeActionOnly之后会调用invokeAction,马上就要进入我们自己写的action里面了。
//DefaultActionInvocation.java  代码有省略
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();

String timerKey = "invokeAction: " + proxy.getActionName();

boolean methodCalled = false;
Object methodResult = null;
Method method = null;
try {
method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
}

if (!methodCalled) {
//OK,我们已经进入 自己写的action了
methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);
}

return saveResult(actionConfig, methodResult);
}


这里我们只是分析了控制部分的代码,action中的成员变量的值是什么时候写入的,我们写的action返回一个success之后struts又干了什么,struts的启动过程是怎么样的,这都是一个一个的大问题,我会在后面的博客里和大家一一道来。

另外,今天glt回来了

glt是谁?

我女朋友

呵呵 她刚从成都飞回来

辛苦了~~~

参考资料
http://www.cnblogs.com/liuling/p/2013-8-10-01.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息