struts2 处理请求流程分析(结合源码)

http://zhxing.iteye.com/blog/541059

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进行了一系列的初始化工作,这个工作很重要也有点复杂,具体每个初始化的工作的流程怎样,待有空闲的时候再继续分析,网上也有人已经分析过了,如果有兴趣可参照:http://zddava.iteye.com/blog/211795

 

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代码

 

    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());方法的分析

   这里的分析参照了:http://zddava.iteye.com/blog/215504

下面来看一下默认使用的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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值