struts2 处理请求流程分析(结合源码)
2014-05-26 14:52
423 查看
/article/4180531.html
struts2 源码版本2.0.11.1
本文是综合网上部分人的分析成果,然后再自己结合源码进行的,分析中如有错误,请指正。
从struts2 中的web.xml的启动配置可以看出,首先分析的是FilterDispatcher 这个过滤器类。
1、过滤器的初始化方法 void init(FilterConfig filterConfig)
Java代码
1.1、createDispatcher(filterConfig);方法,该方法的目的是创建Dispathcher 对象
Java代码
1.2、dispatcher.init();方法,该方法对dispatcher进行了一系列的初始化工作,这个工作很重要也有点复杂,具体每个初始化的工作的流程怎样,待有空闲的时候再继续分析,网上也有人已经分析过了,如果有兴趣可参照:/article/3937334.html
Java代码
1.3、String param = filterConfig.getInitParameter("packages"); 以下的代码。这个步骤载入了packages标签下定义的静态资源。 读取web.xml中 的下面的配置路径还有org.apache.struts2.static,template,org.apache.struts2.interceptor.debugging这三个包空间下边的资源也会作为静态资源载入。
Xml代码
1.4、this.pathPrefixes = parse(packages);这个步骤是对packages 进行解析的。
Java代码
2、过滤器中的doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 方法
2.1、request = prepareDispatcherAndWrapRequest(request, response);分析
我们知道JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰(StrutsRequestWrapper )这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找,而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。
Java代码
简单看下这个方法Dispatcher.getInstance();
Java代码
主要的包装在此方法进行request = dispatcher.wrapRequest(request, getServletContext());
Java代码
2.2、mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());方法的分析
这里的分析参照了:/article/3937337.html
下面来看一下默认使用的ActionMapper实现DefaultActionMapper的#getMapping():
Java代码
主要有6处需要重点说明:
(1) 关于ActionMapping类,它内部封装了如下5个字段:
Java代码
这些在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。
(2) String uri = getUri(request);
这个步骤用于获取客户端发送的请求的URI,源代码如下:
Java代码
这个方法首先判断请求是否来自于一个jsp的include,如果是,那么请求的"javax.servlet.include.servlet_path"属性可以获得include的页面uri,否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,比如http://127.0.0.1:8087/test/jsp/index.jsp?param=1,返回的为/jsp/index.jsp。去掉了ContextPath和查询字符串等。
(3) uri = dropExtension(uri); 负责去掉Action的"扩展名"(默认为"action"),源代码如下:
Java代码
注意,这个步骤对于不是以特地扩展名结尾的请求会返回一个null的uri,进而#getMapping()也会返回null,FilterDispatcher的#doFilter()就会把这次请求当作一个普通请求对待了。
(4) parseNameAndNamespace(uri, mapping, configManager);
此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:
Java代码
(5) handleSpecialParameters(request, mapping); 此方法用于处理Struts框架定义的四种特殊的prefix:
下边是struts2的javadoc里提供的例子:
Method prefix:调用baz的另外一个方法"anotherMethod"而不是"execute"
Html代码
Action prefix:调用anotherAction的"execute"
Html代码
Redirect prefix:将请求重定向,下例中为定向到google
Html代码
Redirect-action prefix:重定向action,下例中为定向到dashboard.action
Html代码
handleSpecialParameters的源代码如下:
Java代码
(6) 处理调用的不是execute方法的情况:
Struts框架也可以处理"name!method"形式的action调用,碰到这种情况,在此处将name和method分别解析出来然后赋给ActionMapping对象。
2.3、dispatcher.serviceAction(request, response, servletContext, mapping);方法分析
Java代码
(1)createContextMap(request, response, mapping, context);方法
Java代码
由此可以看出struts2 对servlet 容器的作用域都进行包装成相应的Map ,然后放在extraContext 统一进行保存。
来看看extraContext 这个map 里放的是全部servlet 容器作用域还有相应的struts2的包装map,和 locale,从下面的源码中可以看出。
Java代码
(2)ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, extraContext, true, false);默认由DefaultActionProxyFactory类创建ActionProxy 。
Java代码
proxy.prepare(); 在这方法中创建ActionInvocation(默认为DefaultActionInvocation),主要由ActionInvocation来调度Action 的实际操作
Java代码
在创建ActionInvocation 的时候有个主要的方法 init();
Java代码
init();方法,该方法创建了Action 和ActionContext
Java代码
创建Action,通过objectFactory 进行创建,而这个类在struts.properties中可以重写这个属性 。默认为SpringObjectFactory:struts.objectFactory=spring,在前面BeanSelectionProvider中通过配置文件为ObjectFactory设置实现类
Java代码
Java代码
proxy.execute();方法是struts2 中流程的重要方法。
Java代码
invoke 方法调用了DefaultActionInvocation的invoke()去实现Action的调用
Java代码
整个方法主要由2个if从句分割,在(1)处的if从句中,主要实现了拦截器的"递归"调用,说它是递归调用,其实是一种非传统的递归。传统的递归应该是函数调用自身,最后达成一定条件后退出,但是这里是将自身的引用作为参数传递给intercept(),然后在intercept()内部再调用DefaultActionInvocation的invoke(),实现了递归调用。
利用这种方式,实现了拦截器和Action的如下的调用逻辑:
Interceptor1
Interceptor2
Interceptor3
Action
Interceptor3
Interceptor2
Interceptor1
最后,当interceptors.hasNext()返回false时,也就是全部拦截器调用完毕之后,函数调用了invokeActionOnly();去实现Action的调用:
Java代码
invokeActionOnly()内部是使用invokeAction()去实现Action的调用的,源代码如下:
Java代码
由这句Object methodResult = method.invoke(action, new Object[0]);可以看出,最后通过反射实现了Action的执行方法的调用。
调用完方法之后,invoke()方法的流程来到了(2)处,由于刚刚调用完Action的那次invoke()调用此时executed为false,所以可以进入此处的if语句。
(2)-1处调用了在PreResultListener中的定义的一些执行Result前的操作。
(2)-2处则根据配置文件中的设置执行Result。
Java代码
于是,最终的调用顺序应该是:
Interceptor1
Interceptor2
Interceptor3
Action
PreResultListener
Result
Interceptor3
Interceptor2
Interceptor1
struts2 源码版本2.0.11.1
本文是综合网上部分人的分析成果,然后再自己结合源码进行的,分析中如有错误,请指正。
从struts2 中的web.xml的启动配置可以看出,首先分析的是FilterDispatcher 这个过滤器类。
1、过滤器的初始化方法 void init(FilterConfig filterConfig)
Java代码
//初始化方法 public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; //获得默认的参数,创建dispathcher 对象 dispatcher = createDispatcher(filterConfig); dispatcher.init(); String param = filterConfig.getInitParameter("packages"); String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging"; if (param != null) { packages = param + " " + packages; } this.pathPrefixes = parse(packages); }
1.1、createDispatcher(filterConfig);方法,该方法的目的是创建Dispathcher 对象
Java代码
protected Dispatcher createDispatcher(FilterConfig filterConfig) { //读取相应过滤器的web.xml 配置 Map<String,String> params = new HashMap<String,String>(); for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) { String name = (String) e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } //可以看出Dispatcher 类包装了ServletContext 和过滤器的web.xml 配置 return new Dispatcher(filterConfig.getServletContext(), params); }
1.2、dispatcher.init();方法,该方法对dispatcher进行了一系列的初始化工作,这个工作很重要也有点复杂,具体每个初始化的工作的流程怎样,待有空闲的时候再继续分析,网上也有人已经分析过了,如果有兴趣可参照:/article/3937334.html
Java代码
public void init() { if (configurationManager == null) { configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME); } //读取properties信息,默认的default.properties init_DefaultProperties(); // [1] //读取xml配置文件,默认的struts-default.xml,struts-plugin.xml,struts.xml init_TraditionalXmlConfigurations(); // [2] //读取用户自定义的struts.properties init_LegacyStrutsProperties(); // [3] //读取FilterDispatcher的配置中所定义的actionPackages属性,传说中的Struts 2 零配置所谓的零配置 init_ZeroConfiguration(); // [4] //自定义的configProviders init_CustomConfigurationProviders(); // [5] //该功能全面被注释 init_MethodConfigurationProvider(); //载入FilterDispatcher传进来的initParams init_FilterInitParameters() ; // [6] //将配置文件中的bean与具体的类映射 init_AliasStandardObjects() ; // [7] //构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 Container container = init_PreloadConfiguration(); init_CheckConfigurationReloading(container); init_CheckWebLogicWorkaround(container); }
1.3、String param = filterConfig.getInitParameter("packages"); 以下的代码。这个步骤载入了packages标签下定义的静态资源。 读取web.xml中 的下面的配置路径还有org.apache.struts2.static,template,org.apache.struts2.interceptor.debugging这三个包空间下边的资源也会作为静态资源载入。
Xml代码
<filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.FilterDispatcher </filter-class> <init-param> <param-name>packages</param-name> <param-value>cn.static.resource</param-value> </init-param> </filter>
1.4、this.pathPrefixes = parse(packages);这个步骤是对packages 进行解析的。
Java代码
protected String[] parse(String packages) { if (packages == null) { return null; } List<String> pathPrefixes = new ArrayList<String>(); StringTokenizer st = new StringTokenizer(packages, ", \n\t"); while (st.hasMoreTokens()) { String pathPrefix = st.nextToken().replace('.', '/'); if (!pathPrefix.endsWith("/")) { pathPrefix += "/"; } pathPrefixes.add(pathPrefix); } return pathPrefixes.toArray(new String[pathPrefixes.size()]); }
2、过滤器中的doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 方法
2.1、request = prepareDispatcherAndWrapRequest(request, response);分析
我们知道JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰(StrutsRequestWrapper )这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找,而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。
Java代码
protected HttpServletRequest prepareDispatcherAndWrapRequest( HttpServletRequest request, HttpServletResponse response) throws ServletException { //获取dispatch 的单例类,是由LocalThread 保存的,保证线程的安全 Dispatcher du = Dispatcher.getInstance(); // Prepare and wrap the request if the cleanup filter hasn't already, // cleanup filter should be // configured first before struts2 dispatcher filter, hence when its // cleanup filter's turn, // static instance of Dispatcher should be null. if (du == null) { //如果为空的话,值保存进LocalThread 中 Dispatcher.setInstance(dispatcher); // prepare the request no matter what - this ensures that the proper // character encoding // is used before invoking the mapper (see WW-9127) // request 编码设置和response 本地化设置 dispatcher.prepare(request, response); } else { dispatcher = du; } try { // Wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding // (ww-1278) //在这里就开始包装 request = dispatcher.wrapRequest(request, getServletContext()); } catch (IOException e) { String message = "Could not wrap servlet request with MultipartRequestWrapper!"; LOG.error(message, e); throw new ServletException(message, e); } return request; }
简单看下这个方法Dispatcher.getInstance();
Java代码
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); //other code public static Dispatcher getInstance() { return instance.get(); }
主要的包装在此方法进行request = dispatcher.wrapRequest(request, getServletContext());
Java代码
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // don't wrap more than once 如果已经包装了,就不用再包装了 if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); //非表单提交的request 封装,主要是图片上传等 if (content_type != null && content_type.indexOf("multipart/form-data") != -1) { MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class); //如果是非表单提交则包装成MultiPartRequestWrapper request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext)); } else { //如果是普通表单提交,在此包装 request = new StrutsRequestWrapper(request); } return request; }
2.2、mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());方法的分析
这里的分析参照了:/article/3937337.html
下面来看一下默认使用的ActionMapper实现DefaultActionMapper的#getMapping():
Java代码
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping();// (1) String uri = getUri(request);// (2) uri = dropExtension(uri);// (3) if (uri == null) { return null; } parseNameAndNamespace(uri, mapping, configManager);// (4) handleSpecialParameters(request, mapping);// (5) if (mapping.getName() == null) { return null; } if (allowDynamicMethodCalls) {// (6) String name = mapping.getName(); int exclamation = name.lastIndexOf("!"); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); mapping.setMethod(name.substring(exclamation + 1)); } } return mapping; }
主要有6处需要重点说明:
(1) 关于ActionMapping类,它内部封装了如下5个字段:
Java代码
private String name;// Action名 private String namespace;// Action名称空间 private String method;// 执行方法 private Map params;// 可以通过set方法设置的参数 private Result result;// 返回的结果
这些在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。
(2) String uri = getUri(request);
这个步骤用于获取客户端发送的请求的URI,源代码如下:
Java代码
String getUri(HttpServletRequest request) { // handle http dispatcher includes. String uri = (String) request.getAttribute("javax.servlet.include.servlet_path"); if (uri != null) { return uri; } uri = RequestUtils.getServletPath(request); if (uri != null && !"".equals(uri)) { return uri; } uri = request.getRequestURI(); return uri.substring(request.getContextPath().length()); }
这个方法首先判断请求是否来自于一个jsp的include,如果是,那么请求的"javax.servlet.include.servlet_path"属性可以获得include的页面uri,否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,比如http://127.0.0.1:8087/test/jsp/index.jsp?param=1,返回的为/jsp/index.jsp。去掉了ContextPath和查询字符串等。
(3) uri = dropExtension(uri); 负责去掉Action的"扩展名"(默认为"action"),源代码如下:
Java代码
String dropExtension(String name) { //extensions 为struts2 的后缀名,可有多个,默认为action // List extensions = new ArrayList() {{ add("action");}}; if (extensions == null) { return name; } Iterator it = extensions.iterator(); //分别遍历后去掉后缀名 while (it.hasNext()) { String extension = "." + (String) it.next(); if (name.endsWith(extension)) { name = name.substring(0, name.length() - extension.length()); return name; } } return null; }
注意,这个步骤对于不是以特地扩展名结尾的请求会返回一个null的uri,进而#getMapping()也会返回null,FilterDispatcher的#doFilter()就会把这次请求当作一个普通请求对待了。
(4) parseNameAndNamespace(uri, mapping, configManager);
此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:
Java代码
void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; // 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1 // dropExtension()后,获得uri为/namespace/name int lastSlash = uri.lastIndexOf("/"); if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) {// alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符全作为名称空间。 namespace = uri.substring(0, lastSlash);// 获得字符串 namespace name = uri.substring(lastSlash + 1);// 获得字符串 name } else { // 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/actionname.action?param=1 // dropExtension()后,获得uri为/namespace1/namespace2/actionname Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash);// 获得 /namespace1/namespace2 namespace = ""; // 如果配置文件中有一个包的namespace是 /namespace1/namespace2,那么namespace为/namespace1/namespace2,name为actionname // 如果配置文件中有一个包的namespace是 /namespace1,那么namespace为/namespace1,name为/namespace2/actionname for (Iterator i = config.getPackageConfigs().values().iterator(); i .hasNext();) { String ns = ((PackageConfig) i.next()).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } } name = uri.substring(namespace.length() + 1); } if (!allowSlashesInActionNames && name != null) {// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } }// 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname mapping.setNamespace(namespace); mapping.setName(name); }
(5) handleSpecialParameters(request, mapping); 此方法用于处理Struts框架定义的四种特殊的prefix:
下边是struts2的javadoc里提供的例子:
Method prefix:调用baz的另外一个方法"anotherMethod"而不是"execute"
Html代码
<a:form action="baz"> <a:textfield label="Enter your name" name="person.name"/> <a:submit value="Create person"/> <a:submit name="method:anotherMethod" value="Cancel"/> </a:form>
Action prefix:调用anotherAction的"execute"
Html代码
<a:form action="baz"> <a:textfield label="Enter your name" name="person.name"/> <a:submit value="Create person"/> <a:submit name="action:anotherAction" value="Cancel"/> </a:form>
Redirect prefix:将请求重定向,下例中为定向到google
Html代码
<a:form action="baz"> <a:textfield label="Enter your name" name="person.name"/> <a:submit value="Create person"/> <a:submit name="redirect:www.google.com" value="Cancel"/> </a:form>
Redirect-action prefix:重定向action,下例中为定向到dashboard.action
Html代码
<a:form action="baz"> <a:textfield label="Enter your name" name="person.name"/> <a:submit value="Create person"/> <a:submit name="redirect-action:dashboard" value="Cancel"/> </a:form>
handleSpecialParameters的源代码如下:
Java代码
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { Set<String> uniqueParameters = new HashSet<String>(); Map parameterMap = request.getParameterMap(); for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) { String key = (String) iterator.next(); if (key.endsWith(".x") || key.endsWith(".y")) {// 去掉图片按钮的位置信息,具体情况我也不是很了解 key = key.substring(0, key.length() - 2); } // 处理四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix if (!uniqueParameters.contains(key)) { ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key); if (parameterAction != null) {// 当发现某种特殊的predix时 parameterAction.execute(key, mapping);// 调用它的execute方法,在DefaultActionMapper的构造函数中定义 uniqueParameters.add(key);// 下边已经break了为什么还要把key加入到排重的Set里呢?? break; } } } }
(6) 处理调用的不是execute方法的情况:
Struts框架也可以处理"name!method"形式的action调用,碰到这种情况,在此处将name和method分别解析出来然后赋给ActionMapping对象。
2.3、dispatcher.serviceAction(request, response, servletContext, mapping);方法分析
Java代码
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { //包装了Http的四个作用域,extraContext 保存了所有的servlet 容器的作用域和struts2 包装的容器作用域 Map<String, Object> extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action //如果之前有ValueStack 值栈存在,则用这个,否则创建一个新的,保存在extraContext 中 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); //获得action 的配置信息 String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); //创建一个ActionProxy ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, extraContext, true, false); //如果method 为空,则设为“excue” proxy.setMethod(method); //保存值栈供struts2 使用 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! //如果result 不为空的话,进行调转 if (mapping.getResult() != null) { Result result = mapping.getResult(); //注入的是ActionInvaction result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back onto the request if (stack != null) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { LOG.error("Could not find action or result", e); sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { throw new ServletException(e); } finally { UtilTimerStack.pop(timerKey); } }
(1)createContextMap(request, response, mapping, context);方法
Java代码
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) { // request map wrapping the http request objects Map requestMap = new RequestMap(request); // parameters map wrapping the http paraneters. Map params = null; if (mapping != null) { params = mapping.getParams(); } Map requestParams = new HashMap(request.getParameterMap()); if (params != null) { params.putAll(requestParams); } else { params = requestParams; } // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(context); //对上面的http 作用域包装的map 进行封装 Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); //把mapping 也放进map 里 extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); return extraContext; }
由此可以看出struts2 对servlet 容器的作用域都进行包装成相应的Map ,然后放在extraContext 统一进行保存。
来看看extraContext 这个map 里放的是全部servlet 容器作用域还有相应的struts2的包装map,和 locale,从下面的源码中可以看出。
Java代码
public HashMap<String,Object> createContextMap(Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { HashMap<String,Object> extraContext = new HashMap<String,Object>(); extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap)); extraContext.put(ActionContext.SESSION, sessionMap); extraContext.put(ActionContext.APPLICATION, applicationMap); Locale locale; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } else { locale = request.getLocale(); } extraContext.put(ActionContext.LOCALE, locale); //extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode)); extraContext.put(StrutsStatics.HTTP_REQUEST, request); extraContext.put(StrutsStatics.HTTP_RESPONSE, response); extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext); // helpers to get access to request/session/application scope extraContext.put("request", requestMap); extraContext.put("session", sessionMap); extraContext.put("application", applicationMap); extraContext.put("parameters", parameterMap); AttributeMap attrMap = new AttributeMap(extraContext); extraContext.put("attr", attrMap); return extraContext; }
(2)ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, extraContext, true, false);默认由DefaultActionProxyFactory类创建ActionProxy 。
Java代码
public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext, boolean executeResult, boolean cleanupContext) throws Exception { //创建ActionProxy ActionProxy proxy = new DefaultActionProxy(namespace, actionName, extraContext, executeResult, cleanupContext); container.inject(proxy); //为了创建ActionInvocation proxy.prepare(); return proxy; }
proxy.prepare(); 在这方法中创建ActionInvocation(默认为DefaultActionInvocation),主要由ActionInvocation来调度Action 的实际操作
Java代码
public void prepare() throws Exception { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandler != null) { config = unknownHandler.handleUnknownAction(namespace, actionName); } if (config == null) { String message; if ((namespace != null) && (namespace.trim().length() > 0)) { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ namespace, actionName }); } else { message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ actionName }); } throw new ConfigurationException(message); } invocation = new DefaultActionInvocation(objectFactory, unknownHandler, this, extraContext, true, actionEventListener); //如果method 为空,则this.method = "execute"; resolveMethod(); } finally { UtilTimerStack.pop(profileKey); } }
在创建ActionInvocation 的时候有个主要的方法 init();
Java代码
protected DefaultActionInvocation(final ObjectFactory objectFactory, final UnknownHandler handler, final ActionProxy proxy, final Map extraContext, final boolean pushAction, final ActionEventListener actionEventListener) throws Exception { UtilTimerStack.profile("create DefaultActionInvocation: ", new UtilTimerStack.ProfilingBlock<Object>() { public Object doProfiling() throws Exception { DefaultActionInvocation.this.proxy = proxy; DefaultActionInvocation.this.objectFactory = objectFactory; DefaultActionInvocation.this.extraContext = extraContext; DefaultActionInvocation.this.pushAction = pushAction; DefaultActionInvocation.this.unknownHandler = handler; DefaultActionInvocation.this.actionEventListener = actionEventListener; init();//这里 return null; } }); }
init();方法,该方法创建了Action 和ActionContext
Java代码
private void init() throws Exception { Map contextMap = createContextMap(); //创建Action createAction(contextMap); if (pushAction) { //把Action 放进值栈 stack.push(action); } //创建ActionContext invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); // get a new List so we don't get problems with the iterator if someone changes the list List interceptorList = new ArrayList(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator(); }
创建Action,通过objectFactory 进行创建,而这个类在struts.properties中可以重写这个属性 。默认为SpringObjectFactory:struts.objectFactory=spring,在前面BeanSelectionProvider中通过配置文件为ObjectFactory设置实现类
Java代码
protected void createAction(Map contextMap) { // load action String timerKey = "actionCreate: "+proxy.getActionName(); try { UtilTimerStack.push(timerKey); action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch (InstantiationException e) { throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig()); } catch (IllegalAccessException e) { throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig()); } catch (Exception e) { String gripe = ""; if (proxy == null) { gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad"; } else if (proxy.getConfig() == null) { gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?"; } else if (proxy.getConfig().getClassName() == null) { gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; } else { gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'"; } gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]"); throw new XWorkException(gripe, e, proxy.getConfig()); } finally { UtilTimerStack.pop(timerKey); } if (actionEventListener != null) { action = actionEventListener.prepare(action, stack); } } public Object buildAction(String actionName, String namespace, ActionConfig config, Map extraContext) throws Exception { return buildBean(config.getClassName(), extraContext); } public Object buildBean(String className, Map extraContext) throws Exception { return buildBean(className, extraContext, true); } public Object buildBean(String className, Map extraContext, boolean injectInternal) throws Exception { Class clazz = getClassInstance(className); Object obj = buildBean(clazz, extraContext); if (injectInternal) { injectInternalBeans(obj); } return obj; }
Java代码
protected Object injectInternalBeans(Object obj) { if (obj != null && container != null) { container.inject(obj); } return obj; }
proxy.execute();方法是struts2 中流程的重要方法。
Java代码
public String execute() throws Exception { ActionContext nestedContext = ActionContext.getContext(); ActionContext.setContext(invocation.getInvocationContext()); String retCode = null; String profileKey = "execute: "; try { UtilTimerStack.push(profileKey); //这个是重点,主要的拦截器功能在这实现,执行返回跳转的字符串 retCode = invocation.invoke(); } finally { if (cleanupContext) { ActionContext.setContext(nestedContext); } UtilTimerStack.pop(profileKey); } return retCode; }
invoke 方法调用了DefaultActionInvocation的invoke()去实现Action的调用
Java代码
public String invoke() throws Exception { ...... try { ...... if (interceptors.hasNext()) {// (1) final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); UtilTimerStack.profile("interceptor: "+interceptor.getName(), new UtilTimerStack.ProfilingBlock<String>() { public String doProfiling() throws Exception { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); return null; } }); } else { resultCode = invokeActionOnly(); } if (!executed) {// (2) if (preResultListeners != null) {// (2)-1 for (Iterator iterator = preResultListeners.iterator(); iterator.hasNext();) { PreResultListener listener = (PreResultListener) iterator.next(); String _profileKey="preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } if (proxy.getExecuteResult()) {// (2)-2 executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
整个方法主要由2个if从句分割,在(1)处的if从句中,主要实现了拦截器的"递归"调用,说它是递归调用,其实是一种非传统的递归。传统的递归应该是函数调用自身,最后达成一定条件后退出,但是这里是将自身的引用作为参数传递给intercept(),然后在intercept()内部再调用DefaultActionInvocation的invoke(),实现了递归调用。
利用这种方式,实现了拦截器和Action的如下的调用逻辑:
Interceptor1
Interceptor2
Interceptor3
Action
Interceptor3
Interceptor2
Interceptor1
最后,当interceptors.hasNext()返回false时,也就是全部拦截器调用完毕之后,函数调用了invokeActionOnly();去实现Action的调用:
Java代码
public String invokeActionOnly() throws Exception { return invokeAction(getAction(), proxy.getConfig()); }
invokeActionOnly()内部是使用invokeAction()去实现Action的调用的,源代码如下:
Java代码
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { String methodName = proxy.getMethod(); if (LOG.isDebugEnabled()) { LOG.debug("Executing action method = " + actionConfig.getMethodName()); } String timerKey = "invokeAction: "+proxy.getActionName(); try { UtilTimerStack.push(timerKey); Method method; try { method = getAction().getClass().getMethod(methodName, new Class[0]); } catch (NoSuchMethodException e) { try { String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); method = getAction().getClass().getMethod(altMethodName, new Class[0]); } catch (NoSuchMethodException e1) { throw e; } } Object methodResult = method.invoke(action, new Object[0]); if (methodResult instanceof Result) { this.result = (Result) methodResult; return null; } else { return (String) methodResult; } } catch (NoSuchMethodException e) { throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (actionEventListener != null) { String result = actionEventListener.handleException(t, getStack()); if (result != null) { return result; } } if (t instanceof Exception) { throw(Exception) t; } else { throw e; } } finally { UtilTimerStack.pop(timerKey); } }
由这句Object methodResult = method.invoke(action, new Object[0]);可以看出,最后通过反射实现了Action的执行方法的调用。
调用完方法之后,invoke()方法的流程来到了(2)处,由于刚刚调用完Action的那次invoke()调用此时executed为false,所以可以进入此处的if语句。
(2)-1处调用了在PreResultListener中的定义的一些执行Result前的操作。
(2)-2处则根据配置文件中的设置执行Result。
Java代码
private void executeResult() throws Exception { result = createResult();// 根据配置文件构建Result String timerKey = "executeResult: "+getResultCode(); try { UtilTimerStack.push(timerKey); if (result != null) { result.execute(this); } else if (resultCode != null && !Action.NONE.equals(resultCode)) { throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode(), proxy.getConfig()); } else { if (LOG.isDebugEnabled()) { LOG.debug("No result returned for action "+getAction().getClass().getName()+" at "+proxy.getConfig().getLocation()); } } } finally { UtilTimerStack.pop(timerKey); } }
于是,最终的调用顺序应该是:
Interceptor1
Interceptor2
Interceptor3
Action
PreResultListener
Result
Interceptor3
Interceptor2
Interceptor1
相关文章推荐
- struts2 处理请求流程分析(结合源码)2
- struts2 处理请求流程分析(结合源码)2
- struts2 处理请求流程分析(结合源码)1
- struts2 处理请求流程分析(结合源码)3
- struts2 处理请求流程分析(结合源码)3
- struts2 处理请求流程分析(结合源码)1
- Struts2源码粗略分析三:请求处理流程分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析
- Struts2请求处理流程及源码分析