struts源码之九
strtsu2完成request的封装后,就创建ActionMapping
ActionMapping的创建过程也很明了。
ActionMapping mapping = prepare.findActionMapping(request, response, true);
首先,去 request中寻找是否有,如果有则返回,否则创建一个。
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; }
注意关键的地方是通过Dispatcher取得容器Container创建,传入的参数是request和ConfigurationManager
dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
request不必多说,ConfigurationManager在初始化的时候也说了,是配置文件的管理类,包括了各种provider
默认的实现是DefaultActionMapper
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = getUri(request); int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; uri = dropExtension(uri, mapping); if (uri == null) { return null; } parseNameAndNamespace(uri, mapping, configManager); handleSpecialParameters(request, mapping); if (mapping.getName() == null) { return null; } parseActionName(mapping); return mapping; }
在进一步了解之前,我们先看看ActionMapping。
public class ActionMapping { private String name; private String namespace; private String method; private String extension; private Map<String, Object> params; private Result result;
ActionMapping是一个实体类,对应的就是一个action所包含的元素,比如名称,民名空间,方法,参数,返回值以及可能的扩展。
返回ActionMapping的创建,首先是初始化了一个空的ActionMapping。
然后取得当前请求的URI
ActionMapping mapping = new ActionMapping(); String uri = getUri(request);
从request中取得uri的方法如下
protected 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()); }
然后从uri中去掉action请求的后缀。
比如xxx.xxx.do,则去掉.do
protected String dropExtension(String name, ActionMapping mapping) { if (extensions == null) { return name; } for (String ext : extensions) { if ("".equals(ext)) { // This should also handle cases such as /foo/bar-1.0/description. It is tricky to // distinquish /foo/bar-1.0 but perhaps adding a numeric check in the future could // work int index = name.lastIndexOf('.'); if (index == -1 || name.indexOf('/', index) >= 0) { return name; } } else { String extension = "." + ext; if (name.endsWith(extension)) { name = name.substring(0, name.length() - extension.length()); mapping.setExtension(ext); return name; } } } return null; }
然后从uri中分析action请求中的name和namespace
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { // Simply select the namespace as everything before the last slash namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); } else { // Try to find the namespace in those defined, defaulting to "" Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } } name = uri.substring(namespace.length() + 1); // Still none found, use root namespace if found if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } mapping.setNamespace(namespace); mapping.setName(name); }
分析的过程也很简单,分析完成后放入mapping中。需要注意的是在分析的过程中读取配置文件,通过Configuration完成的。
Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } }
处理完成,然后处理特殊参数
handleSpecialParameters(request, mapping);
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) { // handle special parameter prefixes. Set<String> uniqueParameters = new HashSet<String>(); Map parameterMap = request.getParameterMap(); for (Iterator iterator = parameterMap.keySet().iterator(); iterator .hasNext();) { String key = (String) iterator.next(); // Strip off the image button location info, if found if (key.endsWith(".x") || key.endsWith(".y")) { key = key.substring(0, key.length() - 2); } // Ensure a parameter doesn't get processed twice if (!uniqueParameters.contains(key)) { ParameterAction parameterAction = (ParameterAction) prefixTrie .get(key); if (parameterAction != null) { parameterAction.execute(key, mapping); uniqueParameters.add(key); break; } } } }
需要注意这个类PrefixTrie,用来匹配前缀的对象
在DefaultActionMapper初始化的时候创建
public DefaultActionMapper() { prefixTrie = new PrefixTrie() { { put(METHOD_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { if (allowDynamicMethodCalls) { mapping.setMethod(key.substring( METHOD_PREFIX.length())); } } }); put(ACTION_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { String name = key.substring(ACTION_PREFIX.length()); if (allowDynamicMethodCalls) { int bang = name.indexOf('!'); if (bang != -1) { String method = name.substring(bang + 1); mapping.setMethod(method); name = name.substring(0, bang); } } mapping.setName(name); } }); put(REDIRECT_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); redirect.setLocation(key.substring(REDIRECT_PREFIX .length())); mapping.setResult(redirect); } }); put(REDIRECT_ACTION_PREFIX, new ParameterAction() { public void execute(String key, ActionMapping mapping) { String location = key.substring(REDIRECT_ACTION_PREFIX .length()); ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); String extension = getDefaultExtension(); if (extension != null && extension.length() > 0) { location += "." + extension; } redirect.setLocation(location); mapping.setResult(redirect); } }); } }; }
然后在handleSpecialParameters才有了处理
ParameterAction parameterAction = (ParameterAction) prefixTrie .get(key); if (parameterAction != null) { parameterAction.execute(key, mapping); uniqueParameters.add(key); break; }
处理的是预先添加的,代码如下:
protected static final String METHOD_PREFIX = "method:"; protected static final String ACTION_PREFIX = "action:"; protected static final String REDIRECT_PREFIX = "redirect:"; protected static final String REDIRECT_ACTION_PREFIX = "redirectAction:";
注意这里使用的回调函数。
parameterAction.execute(key, mapping);
具体的实现则在初始化中的实现
public void execute(String key, ActionMapping mapping) { String location = key.substring(REDIRECT_ACTION_PREFIX .length()); ServletRedirectResult redirect = new ServletRedirectResult(); container.inject(redirect); String extension = getDefaultExtension(); if (extension != null && extension.length() > 0) { location += "." + extension; } redirect.setLocation(location); mapping.setResult(redirect); }
这样就给mapping设置了。
就拿这个例子来说把,创建了ServletRedirectResult
然后放入mapping中,在后续的执行中会使用,我们可以进入ServletRedirectResult 看看这个类,
有一个核心方法doExecute。
在我们调用action返回的时候会调用到这里doExecute。
这里暂时不做详细介绍,
代码如下:
protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { ActionContext ctx = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST); HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE); if (isPathUrl(finalLocation)) { if (!finalLocation.startsWith("/")) { ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager()); String namespace = null; if (mapping != null) { namespace = mapping.getNamespace(); } if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) { finalLocation = namespace + "/" + finalLocation; } else { finalLocation = "/" + finalLocation; } } // if the URL's are relative to the servlet context, append the servlet context path if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) { finalLocation = request.getContextPath() + finalLocation; } ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode()); if (resultConfig != null) { Map<String, String> resultConfigParams = resultConfig.getParams(); for (Map.Entry<String, String> e : resultConfigParams.entrySet()) { if (!getProhibitedResultParams().contains(e.getKey())) { String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue(), invocation); if (!suppressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) { requestParameters.put(e.getKey(), potentialValue); } } } } StringBuilder tmpLocation = new StringBuilder(finalLocation); urlHelper.buildParametersString(requestParameters, tmpLocation, "&"); // add the anchor if (anchor != null) { tmpLocation.append('#').append(anchor); } finalLocation = response.encodeRedirectURL(tmpLocation.toString()); } if (LOG.isDebugEnabled()) { LOG.debug("Redirecting to finalLocation " + finalLocation); } sendRedirect(response, finalLocation); }
回到我们的 DefaultActionMapper中,完成了上面的处理,进入parseActionName
分析action的名称。
parseActionName(mapping);
具体的代码如下:
protected ActionMapping parseActionName(ActionMapping mapping) { if (mapping.getName() == null) { return mapping; } if (allowDynamicMethodCalls) { // handle "name!method" convention. 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; }
如果为空则之间返回,否则处理类似name!method这样的请求 action
处理完成返回.ActionMapping
actionMapping处理完成后放入request中。
if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); }
至此,ActionMapping创建完成。