一、到网上下载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 |
通用运行时异常类 |