您的位置:首页 > 其它

structs2源码分析

2015-10-18 11:29 253 查看
一、到网上下载struts2的源代码:

http://mirrors.cnnic.cn/apache//struts/source/struts-2.3.16.3-src.zip

我把项目的源码路径定向到下载到的源代码:

这份是webwork的核心源码,读取配置文件的核心代码就在于此:



二、下面是struts本尊,基本实现的就是请求的分发:



二、从项目本身去做分析:

好多大牛大师傅都不断强调,去看源码分析源码,但是却从来很少人,告诉你怎么去看,经过多年的摸索我总算找到一个分析源代码的套路,特别对于java工程项目而已,一般来说这些类似于struts这类型的开源项目都会将源代码给开放出去,如果单单打开那些包去看源码哪怕你看个半天其他没啥实际效果,因为没有形成应用场景,例如,没有将请求到处理的执行流程都在脑中思考过一篇,去看人家的源码分析其实尼玛的就是把概念念一遍;

好啦,废话不多讲,上源码分析:

目前我本身的应用使用jetty作为应用服务器:

来看看这部分的代码:



protected void setupWebContext(WebAppContext webapp) {
webapp.addFilter(UrlRewriteFilter.class, "/*", FilterMapping.DEFAULT);
webapp.addFilter(StrutsPrepareAndExecuteFilter.class, "/*", FilterMapping.FORWARD |  FilterMapping.REQUEST);
}


说明:webapp是接收请求上下文的对象,请求过来必须经过webapp的filter;



webapp.addFilter(StrutsPrepareAndExecuteFilter.class, "/*", FilterMapping.FORWARD |  FilterMapping.REQUEST);


这段代码实现拦截请求处理并转发的功能,也就是说请求必将经过

StrutsPrepareAndExecuteFilter类的处理:

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(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), 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();
}

}


可以得到的一些有用的信息,这个类是实现了:StrutsStatics, Filter这两个接口;

1、init实现初始化配置信息;

2、doFilter处理请求、过滤请求;

3、destroy负责清空分发器;

我们再细心点观察思考,就会发现一个比较重要的对象:Dispatcher,在init的时候我们将其填充完成



我们来看看Dispather这个类:



/*
* $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;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.config.FileManagerFactoryProvider;
import com.opensymphony.xwork2.config.FileManagerProvider;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.location.Location;
import com.opensymphony.xwork2.util.location.LocationUtils;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
import freemarker.template.Template;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.config.DefaultBeanSelectionProvider;
import org.apache.struts2.config.DefaultPropertiesProvider;
import org.apache.struts2.config.PropertiesConfigurationProvider;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.util.AttributeMap;
import org.apache.struts2.util.ObjectFactoryDestroyable;
import org.apache.struts2.util.fs.JBossFileManager;
import org.apache.struts2.views.freemarker.FreemarkerManager;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* A utility class the actual dispatcher delegates most of its tasks to. Each instance
* of the primary dispatcher holds an instance of this dispatcher to be shared for
* all requests.
*
* @see org.apache.struts2.dispatcher.FilterDispatcher
*/
public class Dispatcher {

/**
* Provide a logging instance.
*/
private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class);

/**
* Provide a thread local instance.
*/
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();

/**
* Store list of DispatcherListeners.
*/
private static List<DispatcherListener> dispatcherListeners =
new CopyOnWriteArrayList<DispatcherListener>();

/**
* Store ConfigurationManager instance, set on init.
*/
private ConfigurationManager configurationManager;

/**
* Store state of StrutsConstants.STRUTS_DEVMODE setting.
*/
private boolean devMode;

/**
* Store state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
*/
private boolean disableRequestAttributeValueStackLookup;

/**
* Store state of StrutsConstants.STRUTS_I18N_ENCODING setting.
*/
private String defaultEncoding;

/**
* Store state of StrutsConstants.STRUTS_LOCALE setting.
*/
private String defaultLocale;

/**
* Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
*/
private String multipartSaveDir;

/**
* Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting
*/
private String multipartHandlerName;

/**
* Provide list of default configuration files.
*/
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";

/**
* Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND.
* <p/>
* The workaround is for WebLogic.
* We try to autodect WebLogic on Dispatcher init.
* The workaround can also be enabled manually.
*/
private boolean paramsWorkaroundEnabled = false;

/**
* Indicates if Dispatcher should handle exception and call sendError()
* Introduced to allow integration with other frameworks like Spring Security
*/
private boolean handleException;

/**
* Provide the dispatcher instance for the current thread.
*
* @return The dispatcher instance
*/
public static Dispatcher getInstance() {
return instance.get();
}

/**
* Store the dispatcher instance for this thread.
*
* @param instance The instance
*/
public static void setInstance(Dispatcher instance) {
Dispatcher.instance.set(instance);
}

/**
* Add a dispatcher lifecycle listener.
*
* @param listener The listener to add
*/
public static void addDispatcherListener(DispatcherListener listener) {
dispatcherListeners.add(listener);
}

/**
* Remove a specific dispatcher lifecycle listener.
*
* @param listener The listener
*/
public static void removeDispatcherListener(DispatcherListener listener) {
dispatcherListeners.remove(listener);
}

private ServletContext servletContext;
private Map<String, String> initParams;

private ValueStackFactory valueStackFactory;

/**
* Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
*
* @param servletContext Our servlet context
* @param initParams The set of initialization parameters
*/
public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
this.servletContext = servletContext;
this.initParams = initParams;
}

/**
* Modify state of StrutsConstants.STRUTS_DEVMODE setting.
* @param mode New setting
*/
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
devMode = "true".equals(mode);
}

/**
* Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting.
* @param disableRequestAttributeValueStackLookup New setting
*/
@Inject(value=StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required=false)
public void setDisableRequestAttributeValueStackLookup(String disableRequestAttributeValueStackLookup) {
this.disableRequestAttributeValueStackLookup = "true".equalsIgnoreCase(disableRequestAttributeValueStackLookup);
}

/**
* Modify state of StrutsConstants.STRUTS_LOCALE setting.
* @param val New setting
*/
@Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
public void setDefaultLocale(String val) {
defaultLocale = val;
}

/**
* Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
* @param val New setting
*/
@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
public void setDefaultEncoding(String val) {
defaultEncoding = val;
}

/**
* Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
* @param val New setting
*/
@Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
public void setMultipartSaveDir(String val) {
multipartSaveDir = val;
}

@Inject(StrutsConstants.STRUTS_MULTIPART_PARSER)
public void setMultipartHandler(String val) {
multipartHandlerName = val;
}

@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
}

@Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION)
public void setHandleException(String handleException) {
this.handleException = Boolean.parseBoolean(handleException);
}

/**
* Releases all instances bound to this dispatcher instance.
*/
public void cleanup() {

// clean up ObjectFactory
ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class);
if (objectFactory == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed");
}
}
if (objectFactory instanceof ObjectFactoryDestroyable) {
try {
((ObjectFactoryDestroyable)objectFactory).destroy();
}
catch(Exception e) {
// catch any exception that may occurred during destroy() and log it
LOG.error("exception occurred while destroying ObjectFactory [#0]", e, objectFactory.toString());
}
}

// clean up Dispatcher itself for this thread
instance.set(null);

// clean up DispatcherListeners
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherDestroyed(this);
}
}

// clean up all interceptors by calling their destroy() method
Set<Interceptor> interceptors = new HashSet<Interceptor>();
Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values();
for (PackageConfig packageConfig : packageConfigs) {
for (Object config : packageConfig.getAllInterceptorConfigs().values()) {
if (config instanceof InterceptorStackConfig) {
for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) {
interceptors.add(interceptorMapping.getInterceptor());
}
}
}
}
for (Interceptor interceptor : interceptors) {
interceptor.destroy();
}

// Clear container holder when application is unloaded / server shutdown
ContainerHolder.clear();

//cleanup action context
ActionContext.setContext(null);

// clean up configuration
configurationManager.destroyConfiguration();
configurationManager = null;
}

private void init_FileManager() throws ClassNotFoundException {
if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER)) {
final String fileManagerClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER);
final Class<FileManager> fileManagerClass = (Class<FileManager>) Class.forName(fileManagerClassName);
if (LOG.isInfoEnabled()) {
LOG.info("Custom FileManager specified: #0", fileManagerClassName);
}
configurationManager.addContainerProvider(new FileManagerProvider(fileManagerClass, fileManagerClass.getSimpleName()));
} else {
// add any other Struts 2 provided implementations of FileManager
configurationManager.addContainerProvider(new FileManagerProvider(JBossFileManager.class, "jboss"));
}
if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY)) {
final String fileManagerFactoryClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY);
final Class<FileManagerFactory> fileManagerFactoryClass = (Class<FileManagerFactory>) Class.forName(fileManagerFactoryClassName);
if (LOG.isInfoEnabled()) {
LOG.info("Custom FileManagerFactory specified: #0", fileManagerFactoryClassName);
}
configurationManager.addContainerProvider(new FileManagerFactoryProvider(fileManagerFactoryClass));
}
}

private void init_DefaultProperties() {
configurationManager.addContainerProvider(new DefaultPropertiesProvider());
}

private void init_LegacyStrutsProperties() {
configurationManager.addContainerProvider(new PropertiesConfigurationProvider());
}

private void init_TraditionalXmlConfigurations() {
String configPaths = initParams.get("config");
if (configPaths == null) {
configPaths = DEFAULT_CONFIGURATION_PATHS;
}
String[] files = configPaths.split("\\s*[,]\\s*");
for (String file : files) {
if (file.endsWith(".xml")) {
if ("xwork.xml".equals(file)) {
configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false));
} else {
configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
}
} else {
throw new IllegalArgumentException("Invalid configuration file name");
}
}
}

protected XmlConfigurationProvider createXmlConfigurationProvider(String filename, boolean errorIfMissing) {
return new XmlConfigurationProvider(filename, errorIfMissing);
}

protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
}

private void init_CustomConfigurationProviders() {
String configProvs = initParams.get("configProviders");
if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
configurationManager.addContainerProvider(prov);
} catch (InstantiationException e) {
throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Unable to access provider: "+cname, e);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Unable to locate provider class: "+cname, e);
}
}
}
}

private void init_FilterInitParameters() {
configurationManager.addContainerProvider(new ConfigurationProvider() {
public void destroy() {
}

public void init(Configuration configuration) throws ConfigurationException {
}

public void loadPackages() throws ConfigurationException {
}

public boolean needsReload() {
return false;
}

public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
props.putAll(initParams);
}
});
}

private void init_AliasStandardObjects() {
configurationManager.addContainerProvider(new DefaultBeanSelectionProvider());
}

private Container init_PreloadConfiguration() {
Configuration config = configurationManager.getConfiguration();
Container container = config.getContainer();

boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
LocalizedTextUtil.setReloadBundles(reloadi18n);

ContainerHolder.store(container);

return container;
}

private void init_CheckWebLogicWorkaround(Container container) {
// test whether param-access workaround needs to be enabled
if (servletContext != null && servletContext.getServerInfo() != null
&& servletContext.getServerInfo().contains("WebLogic")) {
if (LOG.isInfoEnabled()) {
LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
}
paramsWorkaroundEnabled = true;
} else {
paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
}
}

/**
* Load configurations, including both XML and zero-configuration strategies,
* and update optional settings, including whether to reload configurations and resource files.
*/
public void init() {

if (configurationManager == null) {
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}

try {
init_FileManager();
init_DefaultProperties(); // [1]
init_TraditionalXmlConfigurations(); // [2]
init_LegacyStrutsProperties(); // [3]
init_CustomConfigurationProviders(); // [5]
init_FilterInitParameters() ; // [6]
init_AliasStandardObjects() ; // [7]

Container container = init_PreloadConfiguration();
container.inject(this);
init_CheckWebLogicWorkaround(container);

if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}

protected ConfigurationManager createConfigurationManager(String name) {
return new ConfigurationManager(name);
}

/**
* Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
* <p/>
* This method first creates the action context from the given parameters,
* and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
* After that, the Action method is executed and output channels through the response object.
* Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
* using the 404 return code.
* All other errors are reported by throwing a ServletException.
*
* @param request  the HttpServletRequest object
* @param response the HttpServletResponse object
* @param mapping  the action mapping object
* @throws ServletException when an unknown error occurs (not a 404, but typically something that
*                          would end up as a 5xx by the servlet container)
* @param context Our ServletContext object
*/
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {

Map<String, Object> extraContext = createContextMap(request, response, mapping, context);

// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack 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();

Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
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, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
if (handleException || devMode) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} else {
throw new ServletException(e);
}
} finally {
UtilTimerStack.pop(timerKey);
}
}

/**
* Performs logging of missing action/result configuration exception
*
* @param request current {@link HttpServletRequest}
* @param e {@link ConfigurationException} that occurred
*/
protected void logConfigurationException(HttpServletRequest request, ConfigurationException e) {
// WW-2874 Only log error if in devMode
String uri = request.getRequestURI();
if (request.getQueryString() != null) {
uri = uri + "?" + request.getQueryString();
}
if (devMode) {
LOG.error("Could not find action or result\n#0", e, uri);
} else if (LOG.isWarnEnabled()) {
LOG.warn("Could not find action or result: #0", e, uri);
}
}

/**
* Create a context map containing all the wrapped request objects
*
* @param request The servlet request
* @param response The servlet response
* @param mapping The action mapping
* @param context The servlet context
* @return A map of context objects
*/
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping, ServletContext context) {

// request map wrapping the http request objects
Map requestMap = new RequestMap(request);

// parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
Map params = new HashMap(request.getParameterMap());

// session map wrapping the http session
Map session = new SessionMap(request);

// application map wrapping the ServletContext
Map application = new ApplicationMap(context);

Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);

if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}

/**
* Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
* <tt>Action</tt> context.
*
* @param requestMap     a Map of all request attributes.
* @param parameterMap   a Map of all request parameters.
* @param sessionMap     a Map of all session attributes.
* @param applicationMap a Map of all servlet context attributes.
* @param request        the HttpServletRequest object.
* @param response       the HttpServletResponse object.
* @param servletContext the ServletContextmapping object.
* @return a HashMap representing the <tt>Action</tt> context.
*/
public HashMap<String,Object> createContextMap(Map requestMap,
Map parameterMap,
Map sessionMap,
Map applicationMap,
HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext) {
HashMap<String,Object> extraContext = new HashMap<String,Object>();
extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
extraContext.put(ActionContext.SESSION, sessionMap);
extraContext.put(ActionContext.APPLICATION, applicationMap);

Locale locale;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
} else {
locale = request.getLocale();
}

extraContext.put(ActionContext.LOCALE, locale);
//extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));

extraContext.put(StrutsStatics.HTTP_REQUEST, request);
extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);

// helpers to get access to request/session/application scope
extraContext.put("request", requestMap);
extraContext.put("session", sessionMap);
extraContext.put("application", applicationMap);
extraContext.put("parameters", parameterMap);

AttributeMap attrMap = new AttributeMap(extraContext);
extraContext.put("attr", attrMap);

return extraContext;
}

/**
* Return the path to save uploaded files to (this is configurable).
*
* @return the path to save uploaded files to
* @param servletContext Our ServletContext
*/
private String getSaveDir(ServletContext servletContext) {
String saveDir = multipartSaveDir.trim();

if (saveDir.equals("")) {
File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
if (LOG.isInfoEnabled()) {
LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir");
}

if (tempdir != null) {
saveDir = tempdir.toString();
setMultipartSaveDir(saveDir);
}
} else {
File multipartSaveDir = new File(saveDir);

if (!multipartSaveDir.exists()) {
if (!multipartSaveDir.mkdirs()) {
String logMessage;
try {
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'.";
} catch (IOException e) {
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'.";
}
if (devMode) {
LOG.error(logMessage);
} else {
if (LOG.isWarnEnabled()) {
LOG.warn(logMessage);
}
}
}
}
}

if (LOG.isDebugEnabled()) {
LOG.debug("saveDir=" + saveDir);
}

return saveDir;
}

/**
* Prepare a request, including setting the encoding and locale.
*
* @param request The request
* @param response The response
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
// check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
encoding = "UTF-8";
}

Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}

if (encoding != null) {
applyEncoding(request, encoding);
}

if (locale != null) {
response.setLocale(locale);
}

if (paramsWorkaroundEnabled) {
request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
}
}

private void applyEncoding(HttpServletRequest request, String encoding) {
try {
if (!encoding.equals(request.getCharacterEncoding())) {
// if the encoding is already correctly set and the parameters have been already read
// do not try to set encoding because it is useless and will cause an error
request.setCharacterEncoding(encoding);
}
} catch (Exception e) {
LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
}
}

/**
* 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.
* @param servletContext Our ServletContext object
* @return a wrapped request or original request.
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
* @throws java.io.IOException on any error.
*/
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
// don't wrap more than once
if (request instanceof StrutsRequestWrapper) {
return request;
}

String content_type = request.getContentType();
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(servletContext), provider);
} else {
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
}

return request;
}

/**
* On each request it must return a new instance as implementation could be not thread safe
* and thus ensure of resource clean up
*
* @return
*/
protected MultiPartRequest getMultiPartRequest() {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
for (String multiName : multiNames) {
if (multiName.equals(multipartHandlerName)) {
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
if (mpr == null ) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
return mpr;
}

/**
* Removes all the files created by MultiPartRequestWrapper.
*
* @param request the HttpServletRequest object.
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
*/
public void cleanUpRequest(HttpServletRequest request) {
ContainerHolder.clear();
if (!(request instanceof MultiPartRequestWrapper)) {
return;
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
multiWrapper.cleanUp();
}

/**
* Send an HTTP error response code.
*
* @param request  the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @param code     the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes).
* @param e        the Exception that is reported.
* @param ctx      the ServletContext object.
*/
public void sendError(HttpServletRequest request, HttpServletResponse response, ServletContext ctx, int code, Exception e) {
Boolean devModeOverride = FilterDispatcher.getDevModeOverride();
if (devModeOverride != null ? devModeOverride : devMode) {
if (LOG.isDebugEnabled()) {
LOG.debug("Exception occurred during processing request: #0", e, e.getMessage());
}
try {
FreemarkerManager mgr = getContainer().getInstance(FreemarkerManager.class);

freemarker.template.Configuration config = mgr.getConfiguration(ctx);
Template template = config.getTemplate("/org/apache/struts2/dispatcher/error.ftl");

List<Throwable> chain = new ArrayList<Throwable>();
Throwable cur = e;
chain.add(cur);
while ((cur = cur.getCause()) != null) {
chain.add(cur);
}

HashMap<String,Object> data = new HashMap<String,Object>();
data.put("exception", e);
data.put("unknown", Location.UNKNOWN);
data.put("chain", chain);
data.put("locator", new Locator());

Writer writer = new StringWriter();
template.process(data, writer);

response.setContentType("text/html");
response.getWriter().write(writer.toString());
response.getWriter().close();
} catch (Exception exp) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Cannot show problem report!", exp);
}
response.sendError(code, "Unable to show problem report:\n" + exp + "\n\n" + LocationUtils.getLocation(exp));
} catch (IOException ex) {
// we're already sending an error, not much else we can do if more stuff breaks
}
}
} else {
try {
// WW-1977: Only put errors in the request when code is a 500 error
if (code == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
// WW-4103: Only logs error when application error occurred, not Struts error
if (LOG.isErrorEnabled()) {
LOG.error("Exception occurred during processing request: #0", e, e.getMessage());
}
// send a http error response to use the servlet defined error handler
// make the exception availible to the web.xml defined error page
request.setAttribute("javax.servlet.error.exception", e);

// for compatibility
request.setAttribute("javax.servlet.jsp.jspException", e);
}

// send the error response
response.sendError(code, e.getMessage());
} catch (IOException e1) {
// we're already sending an error, not much else we can do if more stuff breaks
}
}
}

/**
* Cleanup any resources used to initialise Dispatcher
*/
public void cleanUpAfterInit() {
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaning up resources used to init Dispatcher");
}
ContainerHolder.clear();
}

/**
* Provide an accessor class for static XWork utility.
*/
public static class Locator {
public Location getLocation(Object obj) {
Location loc = LocationUtils.getLocation(obj);
if (loc == null) {
return Location.UNKNOWN;
}
return loc;
}
}

/**
* Expose the ConfigurationManager instance.
*
* @return The instance
*/
public ConfigurationManager getConfigurationManager() {
return configurationManager;
}

/**
* Modify the ConfigurationManager instance
*
* @param mgr The configuration manager
* @deprecated should be removed as is used only in tests
*/
public void setConfigurationManager(ConfigurationManager mgr) {
ContainerHolder.clear();
this.configurationManager = mgr;
}

/**
* Expose the dependency injection container.
* @return Our dependency injection container
*/
public Container getContainer() {
if (ContainerHolder.get() != null) {
return ContainerHolder.get();
}
ConfigurationManager mgr = getConfigurationManager();
if (mgr == null) {
throw new IllegalStateException("The configuration manager shouldn't be null");
} else {
Configuration config = mgr.getConfiguration();
if (config == null) {
throw new IllegalStateException("Unable to load configuration");
} else {
Container container = config.getContainer();
ContainerHolder.store(container);
return container;
}
}
}

}


注意到下面的实现:



/**
* Create the Dispatcher instance for a given ServletContext and set of initialization parameters.
*
* @param servletContext Our servlet context
* @param initParams The set of initialization parameters
*/
public Dispatcher(ServletContext servletContext, Map<String, String> initParams) {
this.servletContext = servletContext;
this.initParams = initParams;
}


说明:servletContext就是servlet请求上下文



protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
}


这里面创建StrutsXmlConfigurationProvider;

这个类做了些什么呢?

在看:



/*
* $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.config;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Context;
import com.opensymphony.xwork2.inject.Factory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
* Override Xwork class so we can use an arbitrary config file
*/
public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {

private static final Logger LOG = LoggerFactory.getLogger(StrutsXmlConfigurationProvider.class);
private File baseDir = null;
private String filename;
private String reloadKey;
private ServletContext servletContext;

/**
* Constructs the configuration provider
*
* @param errorIfMissing If we should throw an exception if the file can't be found
*/
public StrutsXmlConfigurationProvider(boolean errorIfMissing) {
this("struts.xml", errorIfMissing, null);
}

/**
* Constructs the configuration provider
*
* @param filename The filename to look for
* @param errorIfMissing If we should throw an exception if the file can't be found
* @param ctx Our ServletContext
*/
public StrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
super(filename, errorIfMissing);
this.servletContext = ctx;
this.filename = filename;
reloadKey = "configurationReload-"+filename;
Map<String,String> dtdMappings = new HashMap<String,String>(getDtdMappings());
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.0//EN", "struts-2.0.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
setDtdMappings(dtdMappings);
File file = new File(filename);
if (file.getParent() != null) {
this.baseDir = file.getParentFile();
}
}

/* (non-Javadoc)
* @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#register(com.opensymphony.xwork2.inject.ContainerBuilder, java.util.Properties)
*/
@Override
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
if (servletContext != null && !containerBuilder.contains(ServletContext.class)) {
containerBuilder.factory(ServletContext.class, new Factory<ServletContext>() {
public ServletContext create(Context context) throws Exception {
return servletContext;
}
});
}
super.register(containerBuilder, props);
}

/* (non-Javadoc)
* @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#init(com.opensymphony.xwork2.config.Configuration)
*/
@Override
public void loadPackages() {
ActionContext ctx = ActionContext.getContext();
ctx.put(reloadKey, Boolean.TRUE);
super.loadPackages();
}

/**
* Look for the configuration file on the classpath and in the file system
*
* @param fileName The file name to retrieve
* @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#getConfigurationUrls
*/
@Override
protected Iterator<URL> getConfigurationUrls(String fileName) throws IOException {
URL url = null;
if (baseDir != null) {
url = findInFileSystem(fileName);
if (url == null) {
return super.getConfigurationUrls(fileName);
}
}
if (url != null) {
List<URL> list = new ArrayList<URL>();
list.add(url);
return list.iterator();
} else {
return super.getConfigurationUrls(fileName);
}
}

protected URL findInFileSystem(String fileName) throws IOException {
URL url = null;
File file = new File(fileName);
if (LOG.isDebugEnabled()) {
LOG.debug("Trying to load file " + file);
}

// Trying relative path to original file
if (!file.exists()) {
file = new File(baseDir, fileName);
}
if (file.exists()) {
try {
url = file.toURI().toURL();
} catch (MalformedURLException e) {
throw new IOException("Unable to convert "+file+" to a URL");
}
}
return url;
}

/**
* Overrides needs reload to ensure it is only checked once per request
*/
@Override
public boolean needsReload() {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
return ctx.get(reloadKey) == null && super.needsReload();
} else {
return super.needsReload();
}

}

public String toString() {
return ("Struts XML configuration provider ("+filename+")");
}
}


注意到里面的:



public StrutsXmlConfigurationProvider(boolean errorIfMissing) {
this("struts.xml", errorIfMissing, null);
}


我相信大多数人看到这里应该已经明白读取struts配置的真是这个类;

再来注意下:



private ConfigurationManager configurationManager;


实际上ConfigurationManager 就是整个项目的配置管理类,struts读取到文件系统的东西都是放到ConfigurationManager 所管理的内存区内;

doFilter负责处理请求的过滤,

我们来看看其处理的逻辑:



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);
}


也就是说它是要找到符合findActionMapping的一个map路由,然后executeAction,然后里面在执行serviceAction



Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);


我们看到proxy.execute();就是说代理然后再执行对对应的action:

网上有一张图,能够比较清晰的表达:



依照上图,我们可以看出一个请求在struts的处理大概有如下步骤:

  1、客户端初始化一个指向Servlet容器的请求;

  2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

  3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;

  4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

  5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

  6、ActionProxy创建一个ActionInvocation的实例。

  7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

Struts2的包结构:



以下是包说明:
org.apache.struts2. components
该包封装视图组件,Struts2在视图组件上有了很大加强,不仅增加了组件的属性个数,更新增了几个非常有用的组件,如updownselect、doubleselect、datetimepicker、token、tree等。
另外,Struts2可视化视图组件开始支持主题(theme),缺省情况下,使用自带的缺省主题,如果要自定义页面效果,需要将组件的theme属性设置为simple。
org.apache.struts2. config
该包定义与配置相关的接口和类。实际上,工程中的xml和properties文件的读取和解析都是由WebWork完成的,Struts只做了少量的工作。
org.apache.struts2.dispatcher
Struts2的核心包,最重要的类都放在该包中。
org.apache.struts2.impl
该包只定义了3个类,他们是StrutsActionProxy、StrutsActionProxyFactory、StrutsObjectFactory,这三个类都是对xwork的扩展。
org.apache.struts2.interceptor
定义内置的截拦器。
org.apache.struts2.servlet
用HttpServletRequest相关方法实现principalproxy接口。
org.apache.struts2.util
实用包。
org.apache.struts2.views
提供freemarker、jsp、velocity等不同类型的页面呈现。
根目录下的5个文件说明:
StrutsStatics
Struts常数。常数可以用来获取或设置对象从行动中或其他集合。
RequestUtils
请求处理程序类。此类只有一个方法getServletPath,作用检索当前请求的servlet路径
ServletActionContext
网站的特定的上下文信息
StrutsConstants
该类提供了框架配置键的中心位置用于存储和检索配置设置。
StrutsException
通用运行时异常类
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  structs2 源码分析