自己打断点走的struts流程&拦截器工作原理
①. 请求发送给 StrutsPrepareAndExecuteFilter
②. StrutsPrepareAndExecuteFilter 判定该请求是否是一个 Struts2 请 求(ActionMapping判断),不是就放行。
(根据路径的后缀是 .action或者.doj进行判断)
③. 若该请求是一个 Struts2 请求,则 StrutsPrepareAndExecuteFilter 把请求的处理交给 ActionProxy
④. ActionProxy 创建一个 ActionInvocation 的实例,并进行初始化
⑤. ActionInvocation 实例在调用 Action 的过程前后,涉及到相关拦截 器(Intercepter)的调用。
⑥. Action 执行完毕,ActionInvocation 负责根据 struts.xml 中的配置 找到对应的返回结果。调用结果的 execute 方法,渲染结果。
⑦. 执行各个拦截器 invocation.invoke() 之后的代码
⑧. 把结果发送到客户端
1.页面访问Action:
2.StrutsPrepareAndExecuteFilter过滤器查看:(ActionMapping提取路径判断是否是Struts路径)
StrutsPrepareAndExecuteFilter.java
/* * $Id: DefaultActionSupport.java 651946 2008-04-27 13:41:38Z apetrelli $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts2.dispatcher.ng.filter; import org.apache.struts2.StrutsStatics; import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.ng.ExecuteOperations; import org.apache.struts2.dispatcher.ng.InitOperations; import org.apache.struts2.dispatcher.ng.PrepareOperations; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; import java.util.regex.Pattern; /** * Handles both the preparation and execution phases of the Struts dispatching process. This filter is better to use * when you don't have another filter that needs access to action context information, such as Sitemesh. */ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { protected PrepareOperations prepare; protected ExecuteOperations execute; protected List<Pattern> excludedPatterns = null; public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); Dispatcher dispatcher = null; try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); prepare = new PrepareOperations(dispatcher); execute = new ExecuteOperations(dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { if (dispatcher != null) { dispatcher.cleanUpAfterInit(); } init.cleanup(); } } /** * Callback for post initialization */ protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } } public void destroy() { prepare.cleanupDispatcher(); } }
解释:
(1)首先获取原生request和response。
(2)对request增强:
/* * $Id: DefaultActionSupport.java 651946 2008-04-27 13:41:38Z apetrelli $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts2.dispatcher.ng; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.struts2.RequestUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsException; import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.mapper.ActionMapper; import org.apache.struts2.dispatcher.mapper.ActionMapping; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.regex.Pattern; /** * Contains preparation operations for a request before execution */ public class PrepareOperations { private static final Logger LOG = LoggerFactory.getLogger(PrepareOperations.class); private Dispatcher dispatcher; private static final String STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping"; public static final String CLEANUP_RECURSION_COUNTER = "__cleanup_recursion_counter"; private Logger log = LoggerFactory.getLogger(PrepareOperations.class); @Deprecated public PrepareOperations(ServletContext servletContext, Dispatcher dispatcher) { this.dispatcher = dispatcher; } public PrepareOperations(Dispatcher dispatcher) { this.dispatcher = dispatcher; } /** * Creates the action context and initializes the thread local */ public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null)); ctx = new ActionContext(stack.getContext()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; } /** * Cleans up a request of thread locals */ public void cleanupRequest(HttpServletRequest request) { Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (counterVal != null) { counterVal -= 1; request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal); if (counterVal > 0 ) { if (log.isDebugEnabled()) { log.debug("skipping cleanup counter="+counterVal); } return; } } // always clean up the thread request, even if an action hasn't been executed try { dispatcher.cleanUpRequest(request); } finally { ActionContext.setContext(null); Dispatcher.setInstance(null); } } /** * Assigns the dispatcher to the dispatcher thread local */ public void assignDispatcherToThread() { Dispatcher.setInstance(dispatcher); } /** * Sets the request encoding and locale on the response */ public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); } /** * Wraps the request with the Struts wrapper that handles multipart requests better * @return The new request, if there is one * @throws ServletException */ public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { HttpServletRequest request = oldRequest; 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); ServletActionContext.setRequest(request); } catch (IOException e) { throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); } return request; } /** * Finds and optionally creates an {@link ActionMapping}. It first looks in the current request to see if one * has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the * case of static resource requests or unidentifiable requests for other servlets, for example. */ public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response) { return findActionMapping(request, response, false); } /** * Finds and optionally creates an {@link ActionMapping}. if forceLookup is false, it first looks in the current request to see if one * has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the * case of static resource requests or unidentifiable requests for other servlets, for example. * @param forceLookup if true, the action mapping will be looked up from the ActionMapper instance, ignoring if there is one * in the request or not */ 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) { if (dispatcher.isHandleException() || dispatcher.isDevMode()) { dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } } return mapping; } /** * Cleans up the dispatcher instance */ public void cleanupDispatcher() { if (dispatcher == null) { throw new StrutsException("Something is seriously wrong, Dispatcher is not initialized (null) "); } else { try { dispatcher.cleanup(); } finally { ActionContext.setContext(null); } } } /** * Check whether the request matches a list of exclude patterns. * * @param request The request to check patterns against * @param excludedPatterns list of patterns for exclusion * * @return <tt>true</tt> if the request URI matches one of the given patterns */ public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) { if (excludedPatterns != null) { String uri = RequestUtils.getUri(request); for ( Pattern pattern : excludedPatterns ) { if (pattern.matcher(uri).matches()) { return true; } } } return false; } }
具体的增强方法的实现在Dispatcher.java
/** * Wrap and return the given request or return the original request object. * </p> * This method transparently handles multipart data as a wrapped class around the given request. * Override this method to handle multipart requests in a special way or to handle other types of requests. * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is * flexible - look first to that object before overriding this method to handle multipart data. * * @param request the HttpServletRequest object. * @return a wrapped request or original request. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper * @throws java.io.IOException on any error. * * @since 2.3.17 */ public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = getMultiPartRequest(); LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); } return request; }
MultiPartRequestWrapper.java(增强后的request)
/* * $Id$ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts2.dispatcher.multipart; import com.opensymphony.xwork2.DefaultLocaleProvider; import com.opensymphony.xwork2.LocaleProvider; import com.opensymphony.xwork2.util.LocalizedTextUtil; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.apache.struts2.dispatcher.StrutsRequestWrapper; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Vector; /** * Parse a multipart request and provide a wrapper around the request. The parsing implementation used * depends on the <tt>struts.multipart.parser</tt> setting. It should be set to a class which * extends {@link org.apache.struts2.dispatcher.multipart.MultiPartRequest}. * <p/> * The <tt>struts.multipart.parser</tt> property should be set to <tt>jakarta</tt> for the * Jakarta implementation, <tt>pell</tt> for the Pell implementation and <tt>cos</tt> for the Jason Hunter * implementation. * <p/> * The files are uploaded when the object is instantiated. If there are any errors they are logged using * {@link #addError(String)}. An action handling a multipart form should first check {@link #hasErrors()} * before doing any other processing. * <p/> * An alternate implementation, PellMultiPartRequest, is provided as a plugin. * */ public class MultiPartRequestWrapper extends StrutsRequestWrapper { protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class); private Collection<String> errors; private MultiPartRequest multi; private Locale defaultLocale = Locale.ENGLISH; /** * Process file downloads and log any errors. * * @param multiPartRequest Our MultiPartRequest object * @param request Our HttpServletRequest object * @param saveDir Target directory for any files that we save * @param provider */ public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir, LocaleProvider provider, boolean disableRequestAttributeValueStackLookup) { super(request, disableRequestAttributeValueStackLookup); errors = new ArrayList<String>(); multi = multiPartRequest; defaultLocale = provider.getLocale(); setLocale(request); try { multi.parse(request, saveDir); for (String error : multi.getErrors()) { addError(error); } } catch (IOException e) { if (LOG.isWarnEnabled()) { LOG.warn(e.getMessage(), e); } addError(buildErrorMessage(e, new Object[] {e.getMessage()})); } } public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir, LocaleProvider provider) { this(multiPartRequest, request, saveDir, provider, false); } protected void setLocale(HttpServletRequest request) { if (defaultLocale == null) { defaultLocale = request.getLocale(); } } protected String buildErrorMessage(Throwable e, Object[] args) { String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName(); if (LOG.isDebugEnabled()) { LOG.debug("Preparing error message for key: [#0]", errorKey); } if (LocalizedTextUtil.findText(this.getClass(), errorKey, getLocale(), null, new Object[0]) == null) { return LocalizedTextUtil.findText(this.getClass(), "struts.messages.error.uploading", defaultLocale, null, new Object[] { e.getMessage() }); } else { return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args); } } /** * Get an enumeration of the parameter names for uploaded files * * @return enumeration of parameter names for uploaded files */ public Enumeration<String> getFileParameterNames() { if (multi == null) { return null; } return multi.getFileParameterNames(); } /** * Get an array of content encoding for the specified input field name or <tt>null</tt> if * no content type was specified. * * @param name input field name * @return an array of content encoding for the specified input field name */ public String[] getContentTypes(String name) { if (multi == null) { return null; } return multi.getContentType(name); } /** * Get a {@link java.io.File[]} for the given input field name. * * @param fieldName input field name * @return a File[] object for files associated with the specified input field name */ public File[] getFiles(String fieldName) { if (multi == null) { return null; } return multi.getFile(fieldName); } /** * Get a String array of the file names for uploaded files * * @param fieldName Field to check for file names. * @return a String[] of file names for uploaded files */ public String[] getFileNames(String fieldName) { if (multi == null) { return null; } return multi.getFileNames(fieldName); } /** * Get the filename(s) of the file(s) uploaded for the given input field name. * Returns <tt>null</tt> if the file is not found. * * @param fieldName input field name * @return the filename(s) of the file(s) uploaded for the given input field name or * <tt>null</tt> if name not found. */ public String[] getFileSystemNames(String fieldName) { if (multi == null) { return null; } return multi.getFilesystemName(fieldName); } /** * @see javax.servlet.http.HttpServletRequest#getParameter(String) */ public String getParameter(String name) { return ((multi == null) || (multi.getParameter(name) == null)) ? super.getParameter(name) : multi.getParameter(name); } /** * @see javax.servlet.http.HttpServletRequest#getParameterMap() */ public Map getParameterMap() { Map<String, String[]> map = new HashMap<String, String[]>(); Enumeration enumeration = getParameterNames(); while (enumeration.hasMoreElements()) { String name = (String) enumeration.nextElement(); map.put(name, this.getParameterValues(name)); } return map; } /** * @see javax.servlet.http.HttpServletRequest#getParameterNames() */ public Enumeration getParameterNames() { if (multi == null) { return super.getParameterNames(); } else { return mergeParams(multi.getParameterNames(), super.getParameterNames()); } } /** * @see javax.servlet.http.HttpServletRequest#getParameterValues(String) */ public String[] getParameterValues(String name) { return ((multi == null) || (multi.getParameterValues(name) == null)) ? super.getParameterValues(name) : multi.getParameterValues(name); } /** * Returns <tt>true</tt> if any errors occured when parsing the HTTP multipart request, <tt>false</tt> otherwise. * * @return <tt>true</tt> if any errors occured when parsing the HTTP multipart request, <tt>false</tt> otherwise. */ public boolean hasErrors() { return !errors.isEmpty(); } /** * Returns a collection of any errors generated when parsing the multipart request. * * @return the error Collection. */ public Collection<String> getErrors() { return errors; } /** * Adds an error message when it isn't already added. * * @param anErrorMessage the error message to report. */ protected void addError(String anErrorMessage) { if (!errors.contains(anErrorMessage)) { errors.add(anErrorMessage); } } /** * Merges 2 enumeration of parameters as one. * * @param params1 the first enumeration. * @param params2 the second enumeration. * @return a single Enumeration of all elements from both Enumerations. */ protected Enumeration mergeParams(Enumeration params1, Enumeration params2) { Vector temp = new Vector(); while (params1.hasMoreElements()) { temp.add(params1.nextElement()); } while (params2.hasMoreElements()) { temp.add(params2.nextElement()); } return temp.elements(); } public void cleanUp() { if (multi != null) { multi.cleanUp(); } } }
(3)调用ActionMapping进行提取路径
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) { if (dispatcher.isHandleException() || dispatcher.isDevMode()) { dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } } return mapping; }
ActionMapping封装的路径信息:
/* * $Id$ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts2.dispatcher.mapper; import com.opensymphony.xwork2.Result; import java.util.Map; /** * Simple class that holds the action mapping information used to invoke a * Struts action. The name and namespace are required, but the params map * is optional, and as such may be null. If a params map is supplied, * it <b>must</b> be a mutable map, such as a HashMap. * */ public class ActionMapping { private String name; private String namespace; private String method; private String extension; private Map<String, Object> params; private Result result; /** * Constructs an ActionMapping */ public ActionMapping() {} /** * Constructs an ActionMapping with a default result * * @param result The default result */ public ActionMapping(Result result) { this.result = result; } /** * Constructs an ActionMapping with its values * * @param name The action name * @param namespace The action namespace * @param method The method * @param params The extra parameters */ public ActionMapping(String name, String namespace, String method, Map<String, Object> params) { this.name = name; this.namespace = namespace; this.method = method; this.params = params; } /** * @return The action name */ public String getName() { return name; } /** * @return The action namespace */ public String getNamespace() { return namespace; } /** * @return The extra parameters */ public Map<String, Object> getParams() { return params; } /** * @return The method */ public String getMethod() { if (null != method && "".equals(method)) { return null; } else { return method; } } /** * @return The default result */ public Result getResult() { return result; } /** * @return The extension used during this request */ public String getExtension() { return extension; } /** * @param result The result */ public void setResult(Result result) { this.result = result; } /** * @param name The action name */ public void setName(String name) { this.name = name; } /** * @param namespace The action namespace */ public void setNamespace(String namespace) { this.namespace = namespace; } /** * @param method The method name to call on the action */ public void setMethod(String method) { this.method = method; } /** * @param params The extra parameters for this mapping */ public void setParams(Map<String, Object> params) { this.params = params; } /** * @param extension The extension used in the request */ public void setExtension(String extension) { this.extension = extension; } @Override public String toString() { return "ActionMapping{" + "name='" + name + '\'' + ", namespace='" + namespace + '\'' + ", method='" + method + '\'' + ", extension='" + extension + '\'' + ", params=" + params + ", result=" + (result != null ? result.getClass().getName() : "null") + '}'; } }
3. 由Dispatcher调用ActionProxy处理请求
Dispatcher.class
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action 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); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); ActionProxy proxy = 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 { 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) { logConfigurationException(request, e); sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { if (handleException || devMode) { sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } else { throw new ServletException(e); } } finally { UtilTimerStack.pop(timerKey); } }
④. ActionProxy 创建一个 ActionInvocation 的实例,并进行初始化
DefaultActionProxy.java
/* * $Id$ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.opensymphony.xwork2; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.LocalizedTextUtil; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import com.opensymphony.xwork2.util.profiling.UtilTimerStack; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import java.io.Serializable; import java.util.Locale; /** * The Default ActionProxy implementation * * @author Rainer Hermanns * @author Revised by <a href="mailto:hu_pengfei@yahoo.com.cn">Henry Hu</a> * @author tmjee * @version $Date$ $Id$ * @since 2005-8-6 */ public class DefaultActionProxy implements ActionProxy, Serializable { private static final long serialVersionUID = 3293074152487468527L; private static final Logger LOG = LoggerFactory.getLogger(DefaultActionProxy.class); protected Configuration configuration; protected ActionConfig config; protected ActionInvocation invocation; protected UnknownHandlerManager unknownHandlerManager; protected String actionName; protected String namespace; protected String method; protected boolean executeResult; protected boolean cleanupContext; protected ObjectFactory objectFactory; protected ActionEventListener actionEventListener; private boolean methodSpecified = true; /** * This constructor is private so the builder methods (create*) should be used to create an DefaultActionProxy. * <p/> * The reason for the builder methods is so that you can use a subclass to create your own DefaultActionProxy instance * (like a RMIActionProxy). */ protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { this.invocation = inv; this.cleanupContext = cleanupContext; if (LOG.isDebugEnabled()) { LOG.debug("Creating an DefaultActionProxy for namespace [#0] and action name [#1]", namespace, actionName); } this.actionName = StringEscapeUtils.escapeHtml4(actionName); this.namespace = namespace; this.executeResult = executeResult; this.method = StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.escapeHtml4(methodName)); } @Inject public void setObjectFactory(ObjectFactory factory) { this.objectFactory = factory; } @Inject public void setConfiguration(Configuration config) { this.configuration = config; } @Inject public void setUnknownHandler(UnknownHandlerManager unknownHandlerManager) { this.unknownHandlerManager = unknownHandlerManager; } @Inject(required = false) public void setActionEventListener(ActionEventListener listener) { this.actionEventListener = listener; } public Object getAction() { return invocation.getAction(); } public String getActionName() { return actionName; } public ActionConfig getConfig() { return config; } public void setExecuteResult(boolean executeResult) { this.executeResult = executeResult; } public boolean getExecuteResult() { return executeResult; } public ActionInvocation getInvocation() { return invocation; } public String getNamespace() { return namespace; } 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; } public String getMethod() { return method; } private void resolveMethod() { // if the method is set to null, use the one from the configuration // if the one from the configuration is also null, use "execute" if (StringUtils.isEmpty(this.method)) { this.method = config.getMethodName(); if (StringUtils.isEmpty(this.method)) { this.method = ActionConfig.DEFAULT_METHOD; } methodSpecified = false; } } protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { config = unknownHandlerManager.handleUnknownAction(namespace, actionName); } if (config == null) { throw new ConfigurationException(getErrorMessage()); } resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: " + method + " for action " + actionName); } invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } } protected String getErrorMessage() { if ((namespace != null) && (namespace.trim().length() > 0)) { return LocalizedTextUtil.findDefaultText( XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{namespace, actionName}); } else { return LocalizedTextUtil.findDefaultText( XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{actionName}); } } public boolean isMethodSpecified() { return methodSpecified; } }
⑤. ActionInvocation 实例在调用 Action 的过程前后,涉及到相关拦截 器(Intercepter)的调用。
DefaultActionInvocation.java
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 = interceptors.next(); String interceptorMsg = "interceptor: " + interceptor.getName(); UtilTimerStack.push(interceptorMsg); try { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } } 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) { LOG.trace("Executing PreResultListeners for result [#0]", result); for (Object preResultListener : preResultListeners) { PreResultListener listener = (PreResultListener) preResultListener; String _profileKey = "preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } // now execute the result, if we're supposed to if (proxy.getExecuteResult()) { executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
再看拦截器的调用原理:
拦截器是一个继承了序列化接口的普通接口。其工作原理是讲需要被拦截的对象作为参数传到intercept()方法内,在方法内部对此对象进行处理之后再执行原方法。
拦截器接口:
Interceptor .java(是一个继承序列化接口的类)
/* * Copyright 2002-2006,2009 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.ActionInvocation; import java.io.Serializable; /** * <!-- START SNIPPET: introduction --> * <p/> * An interceptor is a stateless class that follows the interceptor pattern, as * found in {@link javax.servlet.Filter} and in AOP languages. * <p/> * <p/> * <p/> * Interceptors are objects that dynamically intercept Action invocations. * They provide the developer with the opportunity to define code that can be executed * before and/or after the execution of an action. They also have the ability * to prevent an action from executing. Interceptors provide developers a way to * encapulate common functionality in a re-usable form that can be applied to * one or more Actions. * <p/> * <p/> * <p/> * Interceptors <b>must</b> be stateless and not assume that a new instance will be created for each request or Action. * Interceptors may choose to either short-circuit the {@link ActionInvocation} execution and return a return code * (such as {@link com.opensymphony.xwork2.Action#SUCCESS}), or it may choose to do some processing before * and/or after delegating the rest of the procesing using {@link ActionInvocation#invoke()}. * <p/> * <!-- END SNIPPET: introduction --> * <p/> * <p/> * <p/> * <!-- START SNIPPET: parameterOverriding --> * <p/> * Interceptor's parameter could be overriden through the following ways :- * <p/> * <p/> * <p/> * <b>Method 1:</b> * <pre> * <action name="myAction" class="myActionClass"> * <interceptor-ref name="exception"/> * <interceptor-ref name="alias"/> * <interceptor-ref name="params"/> * <interceptor-ref name="servletConfig"/> * <interceptor-ref name="prepare"/> * <interceptor-ref name="i18n"/> * <interceptor-ref name="chain"/> * <interceptor-ref name="modelDriven"/> * <interceptor-ref name="fileUpload"/> * <interceptor-ref name="staticParams"/> * <interceptor-ref name="params"/> * <interceptor-ref name="conversionError"/> * <interceptor-ref name="validation"> * <param name="excludeMethods">myValidationExcudeMethod</param> * </interceptor-ref> * <interceptor-ref name="workflow"> * <param name="excludeMethods">myWorkflowExcludeMethod</param> * </interceptor-ref> * </action> * </pre> * <p/> * <b>Method 2:</b> * <pre> * <action name="myAction" class="myActionClass"> * <interceptor-ref name="defaultStack"> * <param name="validation.excludeMethods">myValidationExcludeMethod</param> * <param name="workflow.excludeMethods">myWorkflowExcludeMethod</param> * </interceptor-ref> * </action> * </pre> * <p/> * <p/> * <p/> * In the first method, the whole default stack is copied and the parameter then * changed accordingly. * <p/> * <p/> * <p/> * In the second method, the 'interceptor-ref' refer to an existing * interceptor-stack, namely defaultStack in this example, and override the validator * and workflow interceptor excludeMethods typically in this case. Note that in the * 'param' tag, the name attribute contains a dot (.) the word before the dot(.) * specifies the interceptor name whose parameter is to be overridden and the word after * the dot (.) specifies the parameter itself. Essetially it is as follows :- * <p/> * <pre> * <interceptor-name>.<parameter-name> * </pre> * <p/> * <b>Note</b> also that in this case the 'interceptor-ref' name attribute * is used to indicate an interceptor stack which makes sense as if it is referring * to the interceptor itself it would be just using Method 1 describe above. * <p/> * <!-- END SNIPPET: parameterOverriding --> * <p/> * <p/> * <b>Nested Interceptor param overriding</b> * <p/> * <!-- START SNIPPET: nestedParameterOverriding --> * <p/> * Interceptor stack parameter overriding could be nested into as many level as possible, though it would * be advisable not to nest it too deep as to avoid confusion, For example, * <pre> * <interceptor name="interceptor1" class="foo.bar.Interceptor1" /> * <interceptor name="interceptor2" class="foo.bar.Interceptor2" /> * <interceptor name="interceptor3" class="foo.bar.Interceptor3" /> * <interceptor name="interceptor4" class="foo.bar.Interceptor4" /> * <interceptor-stack name="stack1"> * <interceptor-ref name="interceptor1" /> * </interceptor-stack> * <interceptor-stack name="stack2"> * <interceptor-ref name="intercetor2" /> * <interceptor-ref name="stack1" /> * </interceptor-stack> * <interceptor-stack name="stack3"> * <interceptor-ref name="interceptor3" /> * <interceptor-ref name="stack2" /> * </interceptor-stack> * <interceptor-stack name="stack4"> * <interceptor-ref name="interceptor4" /> * <interceptor-ref name="stack3" /> * </interceptor-stack> * </pre> * Assuming the interceptor has the following properties * <table border="1" width="100%"> * <tr> * <td>Interceptor</td> * <td>property</td> * </tr> * <tr> * <td>Interceptor1</td> * <td>param1</td> * </tr> * <tr> * <td>Interceptor2</td> * <td>param2</td> * </tr> * <tr> * <td>Interceptor3</td> * <td>param3</td> * </tr> * <tr> * <td>Interceptor4</td> * <td>param4</td> * </tr> * </table> * We could override them as follows :- * <pre> * <action ... > * <!-- to override parameters of interceptor located directly in the stack --> * <interceptor-ref name="stack4"> * <param name="interceptor4.param4"> ... </param> * </interceptor-ref> * </action> * <p/> * <action ... > * <!-- to override parameters of interceptor located under nested stack --> * <interceptor-ref name="stack4"> * <param name="stack3.interceptor3.param3"> ... </param> * <param name="stack3.stack2.interceptor2.param2"> ... </param> * <param name="stack3.stack2.stack1.interceptor1.param1"> ... </param> * </interceptor-ref> * </action> * </pre> * <p/> * <!-- END SNIPPET: nestedParameterOverriding --> * * @author Jason Carreira * @author tmjee * @version $Date$ $Id$ */ public interface Interceptor extends Serializable { /** * Called to let an interceptor clean up any resources it has allocated. */ void destroy(); /** * Called after an interceptor is created, but before any requests are processed using * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving * the Interceptor a chance to initialize any needed resources. */ void init(); /** * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code. * * @param invocation the action invocation * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself. * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}. */ String intercept(ActionInvocation invocation) throws Exception; }
具体的实现类:
ActionAutowiringInterceptor.java intercept(ActionInvocation invocation)是拦截方法
/* * Copyright 2002-2006,2009 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.opensymphony.xwork2.spring.interceptor; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import com.opensymphony.xwork2.spring.SpringObjectFactory; import com.opensymphony.xwork2.util.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.web.context.WebApplicationContext; /** * <!-- START SNIPPET: description --> * TODO: Give a description of the Interceptor. * <!-- END SNIPPET: description --> * * <!-- START SNIPPET: parameters --> * TODO: Describe the paramters for this Interceptor. * <!-- END SNIPPET: parameters --> * * <!-- START SNIPPET: extending --> * TODO: Discuss some possible extension of the Interceptor. * <!-- END SNIPPET: extending --> * * <pre> * <!-- START SNIPPET: example --> * <!-- TODO: Describe how the Interceptor reference will effect execution --> * <action name="someAction" class="com.examples.SomeAction"> * TODO: fill in the interceptor reference. * <interceptor-ref name=""/> * <result name="success">good_result.ftl</result> * </action> * <!-- END SNIPPET: example --> * </pre> * * Autowires action classes to Spring beans. The strategy for autowiring the beans can be configured * by setting the parameter on the interceptor. Actions that need access to the <code>ActionContext</code> * can implements the <code>ApplicationContextAware</code> interface. The context will also be placed on * the action context under the APPLICATION_CONTEXT attribute. * * @author Simon Stewart * @author Eric Hauser */ public class ActionAutowiringInterceptor extends AbstractInterceptor implements ApplicationContextAware { private static final Logger LOG = LoggerFactory.getLogger(ActionAutowiringInterceptor.class); public static final String APPLICATION_CONTEXT = "com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor.applicationContext"; private boolean initialized = false; private ApplicationContext context; private SpringObjectFactory factory; private Integer autowireStrategy; /** * @param autowireStrategy */ public void setAutowireStrategy(Integer autowireStrategy) { this.autowireStrategy = autowireStrategy; } /** * Looks for the <code>ApplicationContext</code> under the attribute that the Spring listener sets in * the servlet context. The configuration is done the first time here instead of in init() since the * <code>ActionContext</code> is not available during <code>Interceptor</code> initialization. * <p/> * Autowires the action to Spring beans and places the <code>ApplicationContext</code> * on the <code>ActionContext</code> * <p/> * TODO Should this check to see if the <code>SpringObjectFactory</code> has already been configured * instead of instantiating a new one? Or is there a good reason for the interceptor to have it's own * factory? * * @param invocation * @throws Exception */ @Override public String intercept(ActionInvocation invocation) throws Exception { if (!initialized) { ApplicationContext applicationContext = (ApplicationContext) ActionContext.getContext().getApplication().get( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (applicationContext == null) { if (LOG.isWarnEnabled()) { LOG.warn("ApplicationContext could not be found. Action classes will not be autowired."); } } else { setApplicationContext(applicationContext); factory = new SpringObjectFactory(); factory.setApplicationContext(getApplicationContext()); if (autowireStrategy != null) { factory.setAutowireStrategy(autowireStrategy.intValue()); } } initialized = true; } if (factory != null) { Object bean = invocation.getAction(); factory.autoWireBean(bean); ActionContext.getContext().put(APPLICATION_CONTEXT, context); } return invocation.invoke(); } /** * @param applicationContext * @throws BeansException */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } /** * @return context */ protected ApplicationContext getApplicationContext() { return context; } }
⑥. Action 执行完毕,ActionInvocation 负责根据 struts.xml 中的配置 找到对应的返回结果。调用结果的 execute 方法,渲染结果。
⑦. 执行各个拦截器 invocation.invoke() 之后的代码
⑧. 把结果发送到客户端