初看struts2源码...一个简单的action流程

    技术2022-05-20  29

    首先是简单的例子:

    package com.tb.struts2.sourceAnalysis.chap1; public class DirectAction { public String execute(){ System.out.println("cross the street"); return "success"; } }

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.action.excludePattern" value="/beckham/.*"></constant> <!-- 默认执行这个package --> <package name="defaultfuck" namespace="/path" extends="struts-default"> <action name="direct" class="com.tb.struts2.sourceAnalysis.chap1.DirectAction"> <result name="success">/ok.jsp</result> </action> </package> </struts> <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <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> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

     

    ******************************

     

     1. 根据filter的url匹配模式过滤请求过来的每个地址,进入到StrutsPrepareAndExecuteFilter类的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); /* 为每个action设置对应的上下文环境 */ prepare.createActionContext(request, response); /* 保证分发器dispatcher的线程安全,使用了ThreadLocal */ prepare.assignDispatcherToThread(); /* 在配置文件中声明了要struts2不去处理哪些路径 * 例如这样<constant name="struts.action.excludePattern" value="/beckham/.*"></constant> * 意思是struts2将不负责处理这样的路径 */ if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { request = prepare.wrapRequest(request); /* * 取得对应的请求在struts2配置文件中的对应信息 * 比如命名空间, action名称。 参数, 调用方法名称,后缀名等等。 */ ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { /* * 这里有个问题,如果web.XML中配置成这样<url-pattern>/*</url-pattern> 那么其实所有的连接都会匹配 * 哪怕是简单的<a href="a.jsp" mce_href="a.jsp">aaa</a>, 有一定效率的影响啊。 */ boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { /* struts2不处理,直接交给后面的filter或者直接映射 */ chain.doFilter(request, response); } } else { /* 一般的action都经过这里,进行下一步,重要!!!! */ execute.executeAction(request, response, mapping); } } } finally { /* 页面跳转完成,对请求信息进行清理。对应的action上下文环境清空,threadLocal清空。 */ prepare.cleanupRequest(request); } }

        2. execute.executeAction(request, response, mapping);方法带路到了Dispatcher类。

    public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { /* 把所有的程序属性和servlet属性合并起来装到一个大的HashMap里边 */ Map<String, Object> extraContext = createContextMap(request, response, mapping, context); /* 试着获取对应的值栈,如果没有获取到,则创建一个新的值栈 */ ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.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(); /* 这一步想当关键!!!!重要!!!!获得对应的action代理 标注1 */ ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { /* 一般简单的页面跳转会走到这一步,获得代理,去调用execute执行,这个方法也很重要 标注2 */ proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { // WW-2874 Only log error if in devMode if(devMode) { LOG.error("Could not find action or result", e); } else { LOG.warn("Could not find action or result", e); } sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }

        OK,这个方法很重要,有2个标注点。    标注1:

    public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { /* 简单的初始化action代理类 */ StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); /* 注入对应信息,主要用来处理@Inject标签 */ container.inject(proxy); /* 代理类的准备,确认要调用action的哪个方法,实例化action,并准备好需要的拦截器等 */ proxy.prepare(); /* 返回代理类 */ return proxy; }

    这个prepare方法也很重要。

    protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); /* 得到对应的ActionConfig,在struts.xml对应的请求的action段信息 */ config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { config = unknownHandlerManager.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); } /* 这里解析了应该去调用哪个方法。如果没有指定,则默认去调用execute方法, 记住,这里只是指定方法,并不是调用,真正的action还没有实例化 */ resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: "+method+" for action "+actionName); } /* 涉及到另一个类DefaultActionInvocation,负责对代理类进行调用,处理 */ invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } }

        来看init方法:

    public void init(ActionProxy proxy) { this.proxy = proxy; /* 那个很大的Map,添加了几个属性,合并后的map */ Map<String, Object> contextMap = createContextMap(); // 获得对应的action环境 ActionContext actionContext = ActionContext.getContext(); if (actionContext != null) { actionContext.setActionInvocation(this); } /* 实例化action!! */ createAction(contextMap); if (pushAction) { stack.push(action); contextMap.put("action", action); } /* 设置 ActionContext */ invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); /* 拦截器的信息处理 */ List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator(); }

     

        标注2 , 代理类的execute执行然后到了DefaultActionInvokcation 的 invoke方法

     

    public String invoke() throws Exception { String profileKey = "invoke: "; try { UtilTimerStack.push(profileKey); if (executed) { throw new IllegalStateException("Action has already executed"); } if (interceptors.hasNext()) { final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); String interceptorMsg = "interceptor: " + interceptor.getName(); UtilTimerStack.push(interceptorMsg); try { /* 这块理解的不是很透彻,incerpet返回的是intercept.invoke(),所以看起来是if语句,其实做了循环,遍历完了所有的拦截器 */ resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } } else { /* 这个else最终还是会被执行到,当遍历完所有的拦截器后 */ resultCode = invokeActionOnly(); } // this is needed because the result will be executed, then control will return to the Interceptor, which will // return above and flow through again if (!executed) { if (preResultListeners != null) { for (Object preResultListener : preResultListeners) { PreResultListener listener = (PreResultListener) preResultListener; String _profileKey = "preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } // 处理结果,去向那个页面! if (proxy.getExecuteResult()) { executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }

     

        同个类下的invokeActionOnly方法经过一次中转到了invokeAction方法

     

    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); boolean methodCalled = false; Object methodResult = null; Method method = null; try { /* 反射获得对应的要执行的Method类 */ method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { // hmm -- OK, try doXxx instead try { String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1); method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e1) { // well, give the unknown handler a shot if (unknownHandlerManager.hasUnknownHandlers()) { try { methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName); methodCalled = true; } catch (NoSuchMethodException e2) { // throw the original one throw e; } } else { throw e; } } } if (!methodCalled) { /* 执行action中的方法! */ methodResult = method.invoke(action, new Object[0]); } /* 如果是Result的子类,则对应的处理,一般都是JSP,且methodResult这个值一般会是SUCCESS,ERROR,INPUT什么的。所有转else */ if (methodResult instanceof Result) { this.explicitResult = (Result) methodResult; // Wire the result automatically container.inject(explicitResult); return null; } else { /* 返回 */ return (String) methodResult; } } catch (NoSuchMethodException e) { throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); } catch (InvocationTargetException e) { // We try to return the source exception. 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); } }

     

    一切都处理就绪,剩下的就是对于对应的页面进行跳转。通过多次调用到ServletDispatcherResult类,这个类主要负责基本的页面跳转

     

    /** * finalLocation要跳转的地址 比如/ok.jsp */ public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Forwarding to location " + finalLocation); } PageContext pageContext = ServletActionContext.getPageContext(); if (pageContext != null) { pageContext.include(finalLocation); } else { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); /* 给他想了个名字 叫 请求分发器,也就是根据页面他负责把页面的路径拿来然后给你请求的页面 */ RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); /* 在有参数的情况下的处理方式,暂时第一个helloworld没带例子 以后再说 */ if (invocation != null && finalLocation != null && finalLocation.length() > 0 && finalLocation.indexOf("?") > 0) { String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1); Map parameters = (Map) invocation.getInvocationContext().getContextMap().get("parameters"); Map queryParams = UrlHelper.parseQueryString(queryString, true); if (queryParams != null && !queryParams.isEmpty()) parameters.putAll(queryParams); } // 如果请求的页面不存在, 页面到404 你懂的 if (dispatcher == null) { response.sendError(404, "result '" + finalLocation + "' not found"); return; } /* 分发页面, 该去哪儿你去哪儿 */ if (!response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) { request.setAttribute("struts.view_uri", finalLocation); request.setAttribute("struts.request_uri", request.getRequestURI()); dispatcher.forward(request, response); } else { dispatcher.include(request, response); } } }

     

    ==============================================================

       简略为如下流程:    --------------------------------------------------------    初始化信息    过滤器             值栈处理             获得Action代理                        初始化代理类(StrutsActionProxy)                        注入信息(@Inject 如果有)                        准备信息                                   指定调用action中哪个方法                                   实例化action             执行代理                        拦截器执行                        执行action中已经指定好的方法                        页面跳转    清空环境    ------------------------------------------------------    到此一个页面的简单的跳转的过程就OVER了,暂时没涉及到过多的东西。在看代码时候发现2个问题,经常看到struts2的作者频繁取出action上下文环境或者又放进去。。。这块不是很明白。    还有对于将信息放在Map和request频繁取出读取的问题,这块也值得探究。不足之处肯定很多,欢迎各位指出,小弟感激不尽。共同学习。

     

     


    最新回复(0)