2019独角兽企业重金招聘Python工程师标准>>>

转载自:http://www.see-source.com/ 源码解析网

网上对于struts2请求处理流程的讲解还是比较多的,有的还是非常详细的,所以这里我就简单地将大概流程总结下,有了个大概印象后理解起源码就会有一定的思路了:

struts2的请求处理过程实际上是在初始化中加载的配置及容器的基础上,通过请求的url分析出命名空间、action名称、方法名称,在利用命名空间检索出该命名空间下配置的所有antion,在通过action名称找到具体的action,生成action实例,如果该action上配置了拦截器则依次调用拦截器,之后调用action中方法名称指定的方法,最后通过配置中的result返回相应视图。

版本:struts2-2.1.6  xwork-2.1.2

下面就通过源码进行分析下:

struts2中处理请求是通过过滤器org.apache.struts2.dispatcher.FilterDispatcher的doFilter()方法实现的,如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;ServletContext servletContext = getServletContext();String timerKey = "FilterDispatcher_doFilter: ";try {// FIXME: this should be refactored better to not duplicate work with the action invocationValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();ActionContext ctx = new ActionContext(stack.getContext());ActionContext.setContext(ctx);UtilTimerStack.push(timerKey);request = prepareDispatcherAndWrapRequest(request, response);ActionMapping mapping;try {mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());} catch (Exception ex) {log.error("error getting ActionMapping", ex);dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);return;}if (mapping == null) {// there is no action in this request, should we look for a static resource?String resourcePath = RequestUtils.getServletPath(request);if ("".equals(resourcePath) && null != request.getPathInfo()) {resourcePath = request.getPathInfo();}if (staticResourceLoader.canHandle(resourcePath)) {staticResourceLoader.findStaticResource(resourcePath, request, response);} else {// this is a normal request, let it pass throughchain.doFilter(request, response);}// The framework did its job herereturn;}dispatcher.serviceAction(request, response, servletContext, mapping);} finally {try {ActionContextCleanUp.cleanUp(req);} finally {UtilTimerStack.pop(timerKey);}}}

前2句比较简单,向上转换req、res为标准接口HttpServletRequest、HttpServletResponse形式。req是org.apache.catalina.connector.RequestFacade的实例,RequestFacade实现了接口HttpServletRequest,而HttpServletRequest继承自ServletRequest接口。第3句,获得servletContext即servlet上下文,通过上下文对象可以访问web.xml描述文件的初始化参数。第4句定义timerKey变量,值是将当前过滤器的类名和当前方法名拼接起来,看下下面的代码发现timerKey用于UtilTimerStack.push(timerKey)和UtilTimerStack.pop(timerKey)俩句,其实这俩句并不属于处理流程的功能代码,而是性能代码,主要是监测下doFilter()方法的执行时间,然后通过日志打印出来,所以这俩句可以不用理会。ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack()句,首先通过dispatcher.getContainer().getInstance(ValueStackFactory.class)从容器中获得type(类型)为ValueStackFactory.class的bean,然后调用该bean的createValueStack()创建一个ValueStack实例。ValueStackFactory.class默认情况下是使用struts-default.xml配置中的 <bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />,即通过上面的容器返回的是个com.opensymphony.xwork2.ognl.OgnlValueStackFactory实例,从名字看出其是个ValueStack工厂,通过调用该工厂的createValueStack()方法返回ValueStack实例。

ValueStack就是通常所说的"值栈",系统每次请求时都会创建个新的ValueStack,其中会保存着本次请求处理的所有中间数据,如:请求的action实例、各种servlet内置对象(request、response、session、application等)、请求参数等。F5看下定义:

public interface ValueStack {public static final String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack";public static final String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp";public abstract Map<String, Object> getContext();public abstract void setDefaultType(Class defaultType);public abstract void setExprOverrides(Map<Object, Object> overrides);public abstract Map<Object, Object> getExprOverrides();public abstract CompoundRoot getRoot();public abstract void setValue(String expr, Object value);public abstract void setValue(String expr, Object value, boolean throwExceptionOnFailure);public abstract String findString(String expr);public abstract Object findValue(String expr);public abstract Object findValue(String expr, Class asType);public abstract Object peek();public abstract Object pop();public abstract void push(Object o);public abstract void set(String key, Object o);public abstract int size();}

是个接口,因为ValueStack本身是堆栈,所以我们看到peek()、pop()、push()等堆栈方法都是有的,其中有个最重要的方法getContext()和getRoot(),getContext()返回的是个Map<String, Object>类型,一般请求中的参数、servlet各种内置对象都是存放在这个Map中。getRoot()返回的就是堆栈数据实际存放的地方,peek()、pop()、push()都是基于其操作的。CompoundRoot是个堆栈类,它继承了java.util.ArrayList,并以此为基础实现了peek()、pop()、push()方法,从而可以独立的作为堆栈使用。这样的话ValueStack就不必在单独实现堆栈功能,只需要在内部创建个CompoundRoot实例就可,其peek()、pop()、push()方法直接调用CompoundRoot的相应方法即可,实际上struts2中的 ValueStack默认实现类就是这样做的。另外在这个堆栈中保存的最典型的数据就是action实例。有ognl知识的朋友知道,ognl中基于搜索的有俩个重要对象:上下文和根对象,实际上struts2在利用ognl时,是将getContext()获得的对象作为ognl的上下文、getRoot()获得的作为根对象。这就是为什么我们在通过struts2标签访问action中属性数据时不需要加"#",而访问request、response、session中数据时需要加"#"的原因了。因为ognl中访问根对象是不需要加"#"的,而访问上下文是需要加"#"的。为了更好的理解这块,建议大家先学习下ognl的使用方法及特性。为了验证上面的说法,我举个例子,看下struts2的<s:property value=""/>标签是如何使用ValueStack的,以及最后如何调用ognl的。

这里还要事先交代下,在处理流程的后面处理中会将ValueStack(引用)存于俩个地:一个是ActionContext,另一个是通过request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, valuesStack)将ValueStack存于request中。<s:property value=""/>标签中使用的ValueStack是通过request获得的。下面看下<s:property value=""/>标签的源码,从struts2的标签定义文件struts-tags.tld中查到,该标签对应的类是org.apache.struts2.views.jsp.PropertyTag,如下:

public class PropertyTag extends ComponentTagSupport {private static final long serialVersionUID = 435308349113743852L;private String defaultValue;private String value;private boolean escape = true;private boolean escapeJavaScript = false;public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {return new Property(stack);}protected void populateParams() {super.populateParams();Property tag = (Property) component;tag.setDefault(defaultValue);tag.setValue(value);tag.setEscape(escape);tag.setEscapeJavaScript(escapeJavaScript);}public void setDefault(String defaultValue) {this.defaultValue = defaultValue;}public void setEscape(boolean escape) {this.escape = escape;}public void setEscapeJavaScript(boolean escapeJavaScript) {this.escapeJavaScript = escapeJavaScript;}public void setValue(String value) {this.value = value;}
}

我们要找下标签的doStartTag(),这个方法会在标签被处理前调用。关于标签这块,建议大家先学下jsp自定义标签的使用。

去它的父类ComponentTagSupport中找下:

public abstract class ComponentTagSupport extends StrutsBodyTagSupport {protected Component component;public abstract Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res);public int doEndTag() throws JspException {component.end(pageContext.getOut(), getBody());component = null;return EVAL_PAGE;}public int doStartTag() throws JspException {component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse());Container container = Dispatcher.getInstance().getContainer();container.inject(component);populateParams();boolean evalBody = component.start(pageContext.getOut());if (evalBody) {return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE;} else {return SKIP_BODY;}}protected void populateParams() {}public Component getComponent() {return component;}
}

找到了。doStartTag()方法第1句,调用getBean(),其中有个参数调用了getStack(),实际上这个方法返回的就是我上面说的ValueStack。F5进入:

public class StrutsBodyTagSupport extends BodyTagSupport {private static final long serialVersionUID = -1201668454354226175L;protected ValueStack getStack() {return TagUtils.getStack(pageContext);}...//省略
}

该方法是在ComponentTagSupport的父类StrutsBodyTagSupport中。其中只有一句TagUtils.getStack(pageContext),pageContext就是servlet的页面上下文内置对象,F5进入TagUtils.getStack(pageContext):

public static ValueStack getStack(PageContext pageContext) {HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();ValueStack stack = (ValueStack) req.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);if (stack == null) {HttpServletResponse res = (HttpServletResponse) pageContext.getResponse();Dispatcher du = Dispatcher.getInstance();if (du == null) {throw new ConfigurationException("The Struts dispatcher cannot be found.  This is usually caused by "+"using Struts tags without the associated filter. Struts tags are only usable when the request "+"has passed through its servlet filter, which initializes the Struts dispatcher needed for this tag.");}stack = du.getContainer().getInstance(ValueStackFactory.class).createValueStack();Map<String, Object> extraContext = du.createContextMap(new RequestMap(req),req.getParameterMap(),new SessionMap(req),new ApplicationMap(pageContext.getServletContext()),req,res,pageContext.getServletContext());extraContext.put(ServletActionContext.PAGE_CONTEXT, pageContext);stack.getContext().putAll(extraContext);req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);// also tie this stack/context to the ThreadLocalActionContext.setContext(new ActionContext(stack.getContext()));} else {// let's make sure that the current page context is in the action contextMap<String, Object> context = stack.getContext();context.put(ServletActionContext.PAGE_CONTEXT, pageContext);AttributeMap attrMap = new AttributeMap(context);context.put("attr", attrMap);}return stack;}

方法第1句,通过页面上下文对象pageContext获得本次请求的request对象,第2句在通过req.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY)获得ValueStack,因为事先struts2已经通过request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, valuesStack)将ValueStack存于request的ServletActionContext.STRUTS_VALUESTACK_KEY属性中,所以通过第2句就可以获得ValueStack,直接return stack返回到ComponentTagSupport类的doStartTag()方法,如下(我在把代码粘下):

public int doStartTag() throws JspException {component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse());Container container = Dispatcher.getInstance().getContainer();container.inject(component);populateParams();boolean evalBody = component.start(pageContext.getOut());if (evalBody) {return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE;} else {return SKIP_BODY;}}

执行完getStack()接着看getBean()方法,在ComponentTagSupport类中可以找到,getBean()方法被定义为抽象方法,所以具体的实现要在其子类PropertyTag中找,F5进入:

public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {return new Property(stack);}

方法只有一句通过new 生成了Property实例,同时将stack作为构造参数传进去,这个stack就是我们上面通过getStack()得到的ValueStack,执行完后程序会重新回到ComponentTagSupport类的doStartTag()方法,我把代码在粘下:

public int doStartTag() throws JspException {component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse());Container container = Dispatcher.getInstance().getContainer();container.inject(component);populateParams();boolean evalBody = component.start(pageContext.getOut());if (evalBody) {return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE;} else {return SKIP_BODY;}}

这样component中实际引用的就是Property实例,第2、3句,调用容器,通过container.inject(component)将Property中需要注入的属性赋值(带有@Inject标注的)。populateParams()句将标签属性添加到component相应属性中,如value、escape。核心在下面这句component.start(pageContext.getOut()),通过ognl访问ValueStack从而获得标签的value值,F5进入Property的start():

public boolean start(Writer writer) {boolean result = super.start(writer);String actualValue = null;if (value == null) {value = "top";}else {value = stripExpressionIfAltSyntax(value);}// exception: don't call findString(), since we don't want the//            expression parsed in this one case. it really//            doesn't make sense, in fact.actualValue = (String) getStack().findValue(value, String.class);try {if (actualValue != null) {writer.write(prepare(actualValue));} else if (defaultValue != null) {writer.write(prepare(defaultValue));}} catch (IOException e) {LOG.info("Could not print out value '" + value + "'", e);}return result;}

直接看(String) getStack().findValue(value, String.class)  调用getStack()获得ValueStack,这个ValueStack实在Property实例生成时通过构造方法传入的。之后调用ValueStack的findValue()方法, 其中参数value就是<s:property value=""/>标签的value属性值。我们在上面说过ValueStack的实现类默认使用的是com.opensymphony.xwork2.ognl.OgnlValueStack类,F5进入其findValue():

public Object findValue(String expr, Class asType) {try {if (expr == null) {return null;}if ((overrides != null) && overrides.containsKey(expr)) {expr = (String) overrides.get(expr);}Object value = ognlUtil.getValue(expr, context, root, asType);if (value != null) {return value;} else {return findInContext(expr);}} catch (OgnlException e) {return findInContext(expr);} catch (Exception e) {logLookupFailure(expr, e);return findInContext(expr);} finally {ReflectionContextState.clear(context);}}

直接看ognlUtil.getValue(expr, context, root, asType)句,可以看到content、root就是我们上面说的ValueStack中getContext()和getRoot()方法中对应的属性值,我们说过它分别对应ognl中的上下文和根对象。ognlUtil是com.opensymphony.xwork2.ognl.OgnlUtil类的实例,是struts2中用于操作ognl而单独封装的管理类, F5进入ognlUtil.getValue()方法:

public Object getValue(String name, Map<String, Object> context, Object root, Class resultType) throws OgnlException {return Ognl.getValue(compile(name), context, root, resultType);}

实际上,上面的方法只是将name做处理后直接调用ognl的getValue()方法。context作为ognl的上下文,root作为ognl的根对象,name是属性名。此时的root中就存放着当前action的实例。return返回的值就是<s:property value=""/>标签最终所获得的值。说道这熟悉ognl的应该已经明白了。

转载于:https://my.oschina.net/u/1023981/blog/119429

struts2请求处理过程源代码分析(1)相关推荐

  1. struts2请求过程源代码分析

    struts2请求过程源代码分析 Struts2是Struts社区和WebWork社区的共同成果.我们甚至能够说,Struts2是WebWork的升级版.他採用的正是WebWork的核心,所以.Str ...

  2. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  3. Android系统默认Home应用程序(Launcher)的启动过程源代码分析(3)

    Step 13.  ActivityStack.startActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/ ...

  4. Android应用程序组件Content Provider的启动过程源代码分析(1)

             通过前面的学习,我们知道在Android系统中,Content Provider可以为不同的应用程序访问相同的数据提供统一的入口.Content Provider一般是运行在独立的进 ...

  5. Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析

    原文地址: http://blog.csdn.net/luoshengyang/article/details/6629298 在前面一篇文章浅谈Android系统进程间通信(IPC)机制Binder ...

  6. Android应用程序绑定服务(bindService)的过程源代码分析

    Android应用程序组件Service与Activity一样,既可以在新的进程中启动,也可以在应用程序进程内部启动:前面我们已经分析了在新的进程中启动Service的过程,本文将要介绍在应用程序内部 ...

  7. Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析(2)...

    注意,这里的参数reply = 0,表示这是一个BC_TRANSACTION命令.         前面我们提到,传给驱动程序的handle值为0,即这里的tr->target.handle = ...

  8. Android系统默认Home应用程序 Launcher 的启动过程源代码分析

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在前面一 ...

  9. Android应用程序安装过程源代码分析(1)

    Android系统在启动的过程中,会启动一个应用程序管理服务PackageManagerService,这个服务负责扫描系统中特定的目录,找到里面的应用程序文件,即以Apk为后缀的文件,然后对这些文件 ...

最新文章

  1. AjaxFileUpload文件上传组件(php+jQuery+ajax)
  2. 网络上可供测试的Web Service
  3. 学习Key与Value的集合hashtable
  4. 测试android兼容性测试工具,Android兼容性测试工具Spoon
  5. apue 2013-03-14
  6. Spring Boot 2.4发布了,但Spring Cloud用户不推荐着急升级
  7. linux防火墙之牛刀小试
  8. Ubuntu中恢复rm命令误删文件
  9. html中什么是围堵标签,HTML回顾(基础标签)
  10. QQ截图自动保存工具分享
  11. iOS第三方库-魔窗Mlink的坑
  12. nofollow的使用以及作用
  13. Channel Access Protocal——EPICS Base-3.16版CA协议 - 字节序与数据类型
  14. Ardunio开发实例-OPT3001数字环境光传感器
  15. opencv之重映射remap
  16. python中图形绘制技术的应用_python数据图形化—— matplotlib 基础应用
  17. java pdm 解析_java解析静态AIS原始数据
  18. 浅谈 Node.js 热更新,了解几点常见的内存泄漏
  19. AE基础界面设置和旋转加载案例
  20. 数据安全--10--PDCA与数据安全治理

热门文章

  1. Rancher 2.2.2 发布,优化 Kubernetes 集群运维
  2. 路印协议受邀参加澳洲新南威尔士政府孵化器Haymarket HQ分享论坛
  3. An internal error occurred during: Launching xxx on WebLogic10.x.
  4. LVS原理详解(3种工作方式8种调度算法)--老男孩
  5. View_01_LayoutInflater的原理、使用方法
  6. Npm 5 package locks
  7. python: how to delete a given item if it exist in the list
  8. 一个button导致的慘案
  9. Nginx 做图片服务器
  10. 如何使用React.js和Heroku快速实现从想法到URL的转变