①. 请求发送给 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() 之后的代码
⑧. 把结果发送到客户端