Struts2核心模块分析
Struts2是基于http协议的一个经典的MVC web框架,其对Servlet部分进行封装,利用java api的其他部分进行业务逻辑处理。
了解struts2的工作过程,以tomcat容器为例,当来自客户端的请求指向tomcat容器,经过一系列(可选)的过滤器。而struts2中的核心部件FilterDispatcher会对请求进行一系列的包装和处理,分析URI问询ActionMapper指向哪一个Action,处理过程如图所示:
1)分析FilterDispatcher
1 /* 2 * $Id$ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 package org.apache.struts2.dispatcher; 23 24 import com.opensymphony.xwork2.ActionContext; 25 import com.opensymphony.xwork2.config.Configuration; 26 import com.opensymphony.xwork2.config.ConfigurationProvider; 27 import com.opensymphony.xwork2.inject.Inject; 28 import com.opensymphony.xwork2.util.ClassLoaderUtil; 29 import com.opensymphony.xwork2.util.ValueStack; 30 import com.opensymphony.xwork2.util.ValueStackFactory; 31 import com.opensymphony.xwork2.util.logging.Logger; 32 import com.opensymphony.xwork2.util.logging.LoggerFactory; 33 import com.opensymphony.xwork2.util.profiling.UtilTimerStack; 34 import org.apache.struts2.RequestUtils; 35 import org.apache.struts2.StrutsStatics; 36 import org.apache.struts2.dispatcher.mapper.ActionMapper; 37 import org.apache.struts2.dispatcher.mapper.ActionMapping; 38 import org.apache.struts2.dispatcher.ng.filter.FilterHostConfig; 39 40 import javax.servlet.Filter; 41 import javax.servlet.FilterChain; 42 import javax.servlet.FilterConfig; 43 import javax.servlet.ServletContext; 44 import javax.servlet.ServletException; 45 import javax.servlet.ServletRequest; 46 import javax.servlet.ServletResponse; 47 import javax.servlet.http.HttpServletRequest; 48 import javax.servlet.http.HttpServletResponse; 49 import java.io.IOException; 50 import java.util.Enumeration; 51 import java.util.HashMap; 52 import java.util.Map; 53 54 /** 55 * Master filter for Struts that handles four distinct 56 * responsibilities: 57 * <p/> 58 * <ul> 59 * <p/> 60 * <li>Executing actions</li> 61 * <p/> 62 * <li>Cleaning up the {@link ActionContext} (see note)</li> 63 * <p/> 64 * <li>Serving static content</li> 65 * <p/> 66 * <li>Kicking off XWork's interceptor chain for the request lifecycle</li> 67 * <p/> 68 * </ul> 69 * <p/> 70 * <p/> <b>IMPORTANT</b>: this filter must be mapped to all requests. Unless you know exactly what you are doing, always 71 * map to this URL pattern: /* 72 * <p/> 73 * <p/> <b>Executing actions</b> 74 * <p/> 75 * <p/> This filter executes actions by consulting the {@link ActionMapper} and determining if the requested URL should 76 * invoke an action. If the mapper indicates it should, <b>the rest of the filter chain is stopped</b> and the action is 77 * invoked. This is important, as it means that filters like the SiteMesh filter must be placed <b>before</b> this 78 * filter or they will not be able to decorate the output of actions. 79 * <p/> 80 * <p/> <b>Cleaning up the {@link ActionContext}</b> 81 * <p/> 82 * <p/> This filter will also automatically clean up the {@link ActionContext} for you, ensuring that no memory leaks 83 * take place. However, this can sometimes cause problems integrating with other products like SiteMesh. See {@link 84 * ActionContextCleanUp} for more information on how to deal with this. 85 * <p/> 86 * <p/> <b>Serving static content</b> 87 * <p/> 88 * <p/> This filter also serves common static content needed when using various parts of Struts, such as JavaScript 89 * files, CSS files, etc. It works by looking for requests to /struts/*, and then mapping the value after "/struts/" 90 * to common packages in Struts and, optionally, in your class path. By default, the following packages are 91 * automatically searched: 92 * <p/> 93 * <ul> 94 * <p/> 95 * <li>org.apache.struts2.static</li> 96 * <p/> 97 * <li>template</li> 98 * <p/> 99 * </ul> 100 * <p/> 101 * <p/> This means that you can simply request /struts/xhtml/styles.css and the XHTML UI theme's default stylesheet 102 * will be returned. Likewise, many of the AJAX UI components require various JavaScript files, which are found in the 103 * org.apache.struts2.static package. If you wish to add additional packages to be searched, you can add a comma 104 * separated (space, tab and new line will do as well) list in the filter init parameter named "packages". <b>Be 105 * careful</b>, however, to expose any packages that may have sensitive information, such as properties file with 106 * database access credentials. 107 * <p/> 108 * <p/> 109 * <p/> 110 * <p> 111 * <p/> 112 * This filter supports the following init-params: 113 * <!-- START SNIPPET: params --> 114 * <p/> 115 * <ul> 116 * <p/> 117 * <li><b>config</b> - a comma-delimited list of XML configuration files to load.</li> 118 * <p/> 119 * <li><b>actionPackages</b> - a comma-delimited list of Java packages to scan for Actions.</li> 120 * <p/> 121 * <li><b>configProviders</b> - a comma-delimited list of Java classes that implement the 122 * {@link ConfigurationProvider} interface that should be used for building the {@link Configuration}.</li> 123 * <p/> 124 * <li><b>loggerFactory</b> - The class name of the {@link LoggerFactory} implementation.</li> 125 * <p/> 126 * <li><b>*</b> - any other parameters are treated as framework constants.</li> 127 * <p/> 128 * </ul> 129 * <p/> 130 * <!-- END SNIPPET: params --> 131 * <p/> 132 * </p> 133 * <p/> 134 * To use a custom {@link Dispatcher}, the <code>createDispatcher()</code> method could be overriden by 135 * the subclass. 136 * 137 * @version $Date$ $Id$ 138 * @deprecated Since Struts 2.1.3, use {@link org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter} instead or 139 * {@link org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter} and {@link org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter} 140 * if needing using the {@link ActionContextCleanUp} filter in addition to this one 141 * 142 * @see ActionMapper 143 * @see ActionContextCleanUp 144 * @see org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter 145 * @see org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter 146 * @see org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter 147 */ 148 public class FilterDispatcher implements StrutsStatics, Filter { 149 150 /** 151 * Provide a logging instance. 152 */ 153 private Logger log; 154 155 /** 156 * Provide ActionMapper instance, set by injection. 157 */ 158 private ActionMapper actionMapper; 159 160 /** 161 * Provide FilterConfig instance, set on init. 162 */ 163 private FilterConfig filterConfig; 164 165 /** 166 * Expose Dispatcher instance to subclass. 167 */ 168 protected Dispatcher dispatcher; 169 170 /** 171 * Loads static resources, set by injection. 172 */ 173 protected StaticContentLoader staticResourceLoader; 174 175 /** 176 * Maintains per-request override of devMode configuration. 177 */ 178 private static ThreadLocal<Boolean> devModeOverride = new InheritableThreadLocal<Boolean>(); 179 180 /** 181 * Initializes the filter by creating a default dispatcher 182 * and setting the default packages for static resources. 183 * 184 * @param filterConfig The filter configuration 185 */ 186 public void init(FilterConfig filterConfig) throws ServletException { 187 try { 188 this.filterConfig = filterConfig; 189 190 initLogging(); 191 192 dispatcher = createDispatcher(filterConfig); 193 dispatcher.init(); 194 dispatcher.getContainer().inject(this); 195 196 staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); 197 } finally { 198 ActionContext.setContext(null); 199 } 200 } 201 202 private void initLogging() { 203 String factoryName = filterConfig.getInitParameter("loggerFactory"); 204 if (factoryName != null) { 205 try { 206 Class cls = ClassLoaderUtil.loadClass(factoryName, this.getClass()); 207 LoggerFactory fac = (LoggerFactory) cls.newInstance(); 208 LoggerFactory.setLoggerFactory(fac); 209 } catch (InstantiationException e) { 210 System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default"); 211 e.printStackTrace(); 212 } catch (IllegalAccessException e) { 213 System.err.println("Unable to access logger factory: " + factoryName + ", using default"); 214 e.printStackTrace(); 215 } catch (ClassNotFoundException e) { 216 System.err.println("Unable to locate logger factory class: " + factoryName + ", using default"); 217 e.printStackTrace(); 218 } 219 } 220 221 log = LoggerFactory.getLogger(FilterDispatcher.class); 222 223 } 224 225 /** 226 * Calls dispatcher.cleanup, 227 * which in turn releases local threads and destroys any DispatchListeners. 228 * 229 * @see javax.servlet.Filter#destroy() 230 */ 231 public void destroy() { 232 if (dispatcher == null) { 233 log.warn("something is seriously wrong, Dispatcher is not initialized (null) "); 234 } else { 235 try { 236 dispatcher.cleanup(); 237 } finally { 238 ActionContext.setContext(null); 239 } 240 } 241 } 242 243 /** 244 * Set an override of the static devMode value. Do not set this via a 245 * request parameter or any other unprotected method. Using a signed 246 * cookie is one safe way to turn it on per request. 247 * 248 * @param devMode the override value 249 */ 250 public static void overrideDevMode( 251 boolean devMode) 252 { 253 devModeOverride.set(Boolean.valueOf(devMode)); 254 } 255 256 /** 257 * @return Boolean override value, or null if no override 258 */ 259 public static Boolean getDevModeOverride() 260 { 261 return devModeOverride.get(); 262 } 263 264 /** 265 * Create a default {@link Dispatcher} that subclasses can override 266 * with a custom Dispatcher, if needed. 267 * 268 * @param filterConfig Our FilterConfig 269 * @return Initialized Dispatcher 270 */ 271 protected Dispatcher createDispatcher(FilterConfig filterConfig) { 272 Map<String, String> params = new HashMap<String, String>(); 273 for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { 274 String name = (String) e.nextElement(); 275 String value = filterConfig.getInitParameter(name); 276 params.put(name, value); 277 } 278 return createDispatcher(filterConfig.getServletContext(), params); 279 } 280 281 /** 282 * Create a default {@link Dispatcher} that subclasses can override 283 * with a custom Dispatcher, if needed. Called by 284 * createDispatcher(FilterConfig). 285 * 286 * @param ctx ServletContext 287 * @param params parameters from FilterConfig 288 * @return Initialized Dispatcher 289 */ 290 protected Dispatcher createDispatcher(ServletContext ctx, Map<String, String> params) { 291 return new Dispatcher(ctx, params); 292 } 293 294 /** 295 * Modify state of StrutsConstants.STRUTS_STATIC_CONTENT_LOADER setting. 296 * @param staticResourceLoader val New setting 297 */ 298 @Inject 299 public void setStaticResourceLoader(StaticContentLoader staticResourceLoader) { 300 this.staticResourceLoader = staticResourceLoader; 301 } 302 303 /** 304 * Modify ActionMapper instance. 305 * @param mapper New instance 306 */ 307 @Inject 308 public void setActionMapper(ActionMapper mapper) { 309 actionMapper = mapper; 310 } 311 312 /** 313 * Provide a workaround for some versions of WebLogic. 314 * <p/> 315 * Servlet 2.3 specifies that the servlet context can be retrieved from the session. Unfortunately, some versions of 316 * WebLogic can only retrieve the servlet context from the filter config. Hence, this method enables subclasses to 317 * retrieve the servlet context from other sources. 318 * 319 * @return the servlet context. 320 */ 321 protected ServletContext getServletContext() { 322 return filterConfig.getServletContext(); 323 } 324 325 /** 326 * Expose the FilterConfig instance. 327 * 328 * @return Our FilterConfit instance 329 */ 330 protected FilterConfig getFilterConfig() { 331 return filterConfig; 332 } 333 334 /** 335 * Wrap and return the given request, if needed, so as to to transparently 336 * handle multipart data as a wrapped class around the given request. 337 * 338 * @param request Our ServletRequest object 339 * @param response Our ServerResponse object 340 * @return Wrapped HttpServletRequest object 341 * @throws ServletException on any error 342 */ 343 protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException { 344 345 Dispatcher du = Dispatcher.getInstance(); 346 347 // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be 348 // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn, 349 // static instance of Dispatcher should be null. 350 if (du == null) { 351 352 Dispatcher.setInstance(dispatcher); 353 354 // prepare the request no matter what - this ensures that the proper character encoding 355 // is used before invoking the mapper (see WW-9127) 356 dispatcher.prepare(request, response); 357 } else { 358 dispatcher = du; 359 } 360 361 try { 362 // Wrap request first, just in case it is multipart/form-data 363 // parameters might not be accessible through before encoding (ww-1278) 364 request = dispatcher.wrapRequest(request); 365 } catch (IOException e) { 366 String message = "Could not wrap servlet request with MultipartRequestWrapper!"; 367 log.error(message, e); 368 throw new ServletException(message, e); 369 } 370 371 return request; 372 } 373 374 /** 375 * Process an action or handle a request a static resource. 376 * <p/> 377 * The filter tries to match the request to an action mapping. 378 * If mapping is found, the action processes is delegated to the dispatcher's serviceAction method. 379 * If action processing fails, doFilter will try to create an error page via the dispatcher. 380 * <p/> 381 * Otherwise, if the request is for a static resource, 382 * the resource is copied directly to the response, with the appropriate caching headers set. 383 * <p/> 384 * If the request does not match an action mapping, or a static resource page, 385 * then it passes through. 386 * 387 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) 388 */ 389 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 390 391 showDeprecatedWarning(); 392 393 HttpServletRequest request = (HttpServletRequest) req; 394 HttpServletResponse response = (HttpServletResponse) res; 395 ServletContext servletContext = getServletContext(); 396 397 String timerKey = "FilterDispatcher_doFilter: "; 398 try { 399 400 // FIXME: this should be refactored better to not duplicate work with the action invocation 401 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); 402 ActionContext ctx = new ActionContext(stack.getContext()); 403 ActionContext.setContext(ctx); 404 405 UtilTimerStack.push(timerKey); 406 request = prepareDispatcherAndWrapRequest(request, response); 407 ActionMapping mapping; 408 try { 409 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); 410 } catch (Exception ex) { 411 log.error("error getting ActionMapping", ex); 412 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); 413 return; 414 } 415 416 if (mapping == null) { 417 // there is no action in this request, should we look for a static resource? 418 String resourcePath = RequestUtils.getServletPath(request); 419 420 if ("".equals(resourcePath) && null != request.getPathInfo()) { 421 resourcePath = request.getPathInfo(); 422 } 423 424 if (staticResourceLoader.canHandle(resourcePath)) { 425 staticResourceLoader.findStaticResource(resourcePath, request, response); 426 } else { 427 // this is a normal request, let it pass through 428 chain.doFilter(request, response); 429 } 430 // The framework did its job here 431 return; 432 } 433 434 dispatcher.serviceAction(request, response, mapping); 435 436 } finally { 437 dispatcher.cleanUpRequest(request); 438 try { 439 ActionContextCleanUp.cleanUp(req); 440 } finally { 441 UtilTimerStack.pop(timerKey); 442 } 443 devModeOverride.remove(); 444 } 445 } 446 447 private void showDeprecatedWarning() { 448 String msg = 449 "\n\n" + 450 "***********************************************************************\n" + 451 "* WARNING!!! *\n" + 452 "* *\n" + 453 "* >>> FilterDispatcher <<< is deprecated! Please use the new filters! *\n" + 454 "* *\n" + 455 "* This can be a source of unpredictable problems! *\n" + 456 "* *\n" + 457 "* Please refer to the docs for more details! *\n" + 458 "* http://struts.apache.org/2.x/docs/webxml.html *\n" + 459 "* *\n" + 460 "***********************************************************************\n\n"; 461 System.out.println(msg); 462 } 463 }
FilterDispatcher中的主要部件:ActionMapper FilterConfig Dispatcher
FilterConfig:
Servlet容器中过滤器的配置对象,将配置信息传递给过滤器。提供的方法有:
public String getFilterName()返回过滤器的名称
public String getInitParameter(String name)返回指定初始化名称对应的值,name对应下图代码中的param-name,return param-value标签中的值,如果没有则返回空
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter>
public ServletContext getServletContext()返回给调用者一个ServletContext对象
ActionMapper:
此处的ActionMapper是由jee利用@Inject依赖注入,其默认的实现类为DefaultActionMapper,主要方法有:
ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = RequestUtils.getUri(request); int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; uri = dropExtension(uri, mapping); if (uri == null) { return null; } parseNameAndNamespace(uri, mapping, configManager); handleSpecialParameters(request, mapping); return parseActionName(mapping); }
ActionMapping getMappingFromActionName(String actionName);
1 public ActionMapping getMappingFromActionName(String actionName) { 2 ActionMapping mapping = new ActionMapping(); 3 mapping.setName(actionName); 4 return parseActionName(mapping); 5 }
String getUriFromActionMapping(ActionMapping mapping);
1 public String getUriFromActionMapping(ActionMapping mapping) { 2 StringBuilder uri = new StringBuilder(); 3 4 handleNamespace(mapping, uri); 5 handleName(mapping, uri); 6 handleDynamicMethod(mapping, uri); 7 handleExtension(mapping, uri); 8 handleParams(mapping, uri); 9 10 return uri.toString(); 11 }
FilterDispatcher通过注入的ActionMapper实例对象中,利用请求路径和配置文件对象(通常为struts.xml)去逐一匹配nameSpace,action,method和result。如果匹配不成功便放行,如果匹配成功就利用Dispatcher去处理。
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 2 3 showDeprecatedWarning(); 4 5 HttpServletRequest request = (HttpServletRequest) req; 6 HttpServletResponse response = (HttpServletResponse) res; 7 ServletContext servletContext = getServletContext(); 8 9 String timerKey = "FilterDispatcher_doFilter: "; 10 try { 11 12 // FIXME: this should be refactored better to not duplicate work with the action invocation 13 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); 14 ActionContext ctx = new ActionContext(stack.getContext()); 15 ActionContext.setContext(ctx); 16 17 UtilTimerStack.push(timerKey); 18 request = prepareDispatcherAndWrapRequest(request, response); 19 ActionMapping mapping; 20 try { 21 mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); 22 } catch (Exception ex) { 23 log.error("error getting ActionMapping", ex); 24 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); 25 return; 26 } 27 28 if (mapping == null) { 29 // there is no action in this request, should we look for a static resource? 30 String resourcePath = RequestUtils.getServletPath(request); 31 32 if ("".equals(resourcePath) && null != request.getPathInfo()) { 33 resourcePath = request.getPathInfo(); 34 } 35 36 if (staticResourceLoader.canHandle(resourcePath)) { 37 staticResourceLoader.findStaticResource(resourcePath, request, response); 38 } else { 39 // this is a normal request, let it pass through 40 chain.doFilter(request, response);//没有匹配成功 41 } 42 // The framework did its job here 43 return; 44 } 45 46 dispatcher.serviceAction(request, response, mapping);//匹配成功 47 48 } finally { 49 dispatcher.cleanUpRequest(request); 50 try { 51 ActionContextCleanUp.cleanUp(req); 52 } finally { 53 UtilTimerStack.pop(timerKey); 54 } 55 devModeOverride.remove(); 56 } 57 }
Dispatcher
下面贴出Dispatcher的全部代码
1 /* 2 * $Id$ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 package org.apache.struts2.dispatcher; 23 24 import com.opensymphony.xwork2.*; 25 import com.opensymphony.xwork2.config.*; 26 import com.opensymphony.xwork2.config.entities.InterceptorMapping; 27 import com.opensymphony.xwork2.config.entities.InterceptorStackConfig; 28 import com.opensymphony.xwork2.config.entities.PackageConfig; 29 import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider; 30 import com.opensymphony.xwork2.inject.Container; 31 import com.opensymphony.xwork2.inject.ContainerBuilder; 32 import com.opensymphony.xwork2.inject.Inject; 33 import com.opensymphony.xwork2.interceptor.Interceptor; 34 import com.opensymphony.xwork2.util.ClassLoaderUtil; 35 import com.opensymphony.xwork2.util.LocalizedTextUtil; 36 import com.opensymphony.xwork2.util.ValueStack; 37 import com.opensymphony.xwork2.util.ValueStackFactory; 38 import com.opensymphony.xwork2.util.location.LocatableProperties; 39 import com.opensymphony.xwork2.util.location.Location; 40 import com.opensymphony.xwork2.util.location.LocationUtils; 41 import com.opensymphony.xwork2.util.logging.Logger; 42 import com.opensymphony.xwork2.util.logging.LoggerFactory; 43 import com.opensymphony.xwork2.util.profiling.UtilTimerStack; 44 import org.apache.struts2.ServletActionContext; 45 import org.apache.struts2.StrutsConstants; 46 import org.apache.struts2.StrutsException; 47 import org.apache.struts2.StrutsStatics; 48 import org.apache.struts2.config.DefaultBeanSelectionProvider; 49 import org.apache.struts2.config.DefaultPropertiesProvider; 50 import org.apache.struts2.config.PropertiesConfigurationProvider; 51 import org.apache.struts2.config.StrutsXmlConfigurationProvider; 52 import org.apache.struts2.dispatcher.mapper.ActionMapping; 53 import org.apache.struts2.dispatcher.multipart.MultiPartRequest; 54 import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; 55 import org.apache.struts2.util.AttributeMap; 56 import org.apache.struts2.util.ObjectFactoryDestroyable; 57 import org.apache.struts2.util.fs.JBossFileManager; 58 59 import javax.servlet.ServletContext; 60 import javax.servlet.ServletException; 61 import javax.servlet.http.HttpServletRequest; 62 import javax.servlet.http.HttpServletResponse; 63 import java.io.File; 64 import java.io.IOException; 65 import java.util.*; 66 import java.util.concurrent.CopyOnWriteArrayList; 67 68 /** 69 * A utility class the actual dispatcher delegates most of its tasks to. Each instance 70 * of the primary dispatcher holds an instance of this dispatcher to be shared for 71 * all requests. 72 * 73 * @see org.apache.struts2.dispatcher.ng.InitOperations 74 */ 75 public class Dispatcher { 76 77 /** 78 * Provide a logging instance. 79 */ 80 private static final Logger LOG = LoggerFactory.getLogger(Dispatcher.class); 81 82 /** 83 * Provide a thread local instance. 84 */ 85 private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>(); 86 87 /** 88 * Store list of DispatcherListeners. 89 */ 90 private static List<DispatcherListener> dispatcherListeners = 91 new CopyOnWriteArrayList<DispatcherListener>(); 92 93 /** 94 * Store ConfigurationManager instance, set on init. 95 */ 96 private ConfigurationManager configurationManager; 97 98 /** 99 * Store state of StrutsConstants.STRUTS_DEVMODE setting. 100 */ 101 private boolean devMode; 102 103 /** 104 * Store state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting. 105 */ 106 private boolean disableRequestAttributeValueStackLookup; 107 108 /** 109 * Store state of StrutsConstants.STRUTS_I18N_ENCODING setting. 110 */ 111 private String defaultEncoding; 112 113 /** 114 * Store state of StrutsConstants.STRUTS_LOCALE setting. 115 */ 116 private String defaultLocale; 117 118 /** 119 * Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting. 120 */ 121 private String multipartSaveDir; 122 123 /** 124 * Stores the value of {@link StrutsConstants#STRUTS_MULTIPART_PARSER} setting 125 */ 126 private String multipartHandlerName; 127 128 /** 129 * Provide list of default configuration files. 130 */ 131 private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml"; 132 133 /** 134 * Store state of STRUTS_DISPATCHER_PARAMETERSWORKAROUND. 135 * <p/> 136 * The workaround is for WebLogic. 137 * We try to autodect WebLogic on Dispatcher init. 138 * The workaround can also be enabled manually. 139 */ 140 private boolean paramsWorkaroundEnabled = false; 141 142 /** 143 * Indicates if Dispatcher should handle exception and call sendError() 144 * Introduced to allow integration with other frameworks like Spring Security 145 */ 146 private boolean handleException; 147 148 /** 149 * Interface used to handle internal errors or missing resources 150 */ 151 private DispatcherErrorHandler errorHandler; 152 153 /** 154 * Provide the dispatcher instance for the current thread. 155 * 156 * @return The dispatcher instance 157 */ 158 public static Dispatcher getInstance() { 159 return instance.get(); 160 } 161 162 /** 163 * Store the dispatcher instance for this thread. 164 * 165 * @param instance The instance 166 */ 167 public static void setInstance(Dispatcher instance) { 168 Dispatcher.instance.set(instance); 169 } 170 171 /** 172 * Add a dispatcher lifecycle listener. 173 * 174 * @param listener The listener to add 175 */ 176 public static void addDispatcherListener(DispatcherListener listener) { 177 dispatcherListeners.add(listener); 178 } 179 180 /** 181 * Remove a specific dispatcher lifecycle listener. 182 * 183 * @param listener The listener 184 */ 185 public static void removeDispatcherListener(DispatcherListener listener) { 186 dispatcherListeners.remove(listener); 187 } 188 189 private ValueStackFactory valueStackFactory; 190 191 /** 192 * Keeps current reference to external world and must be protected to support class inheritance 193 */ 194 protected ServletContext servletContext; 195 protected Map<String, String> initParams; 196 197 /** 198 * Create the Dispatcher instance for a given ServletContext and set of initialization parameters. 199 * 200 * @param servletContext Our servlet context 201 * @param initParams The set of initialization parameters 202 */ 203 public Dispatcher(ServletContext servletContext, Map<String, String> initParams) { 204 this.servletContext = servletContext; 205 this.initParams = initParams; 206 } 207 208 /** 209 * Modify state of StrutsConstants.STRUTS_DEVMODE setting. 210 * @param mode New setting 211 */ 212 @Inject(StrutsConstants.STRUTS_DEVMODE) 213 public void setDevMode(String mode) { 214 devMode = "true".equals(mode); 215 } 216 217 /** 218 * Modify state of StrutsConstants.DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP setting. 219 * @param disableRequestAttributeValueStackLookup New setting 220 */ 221 @Inject(value=StrutsConstants.STRUTS_DISABLE_REQUEST_ATTRIBUTE_VALUE_STACK_LOOKUP, required=false) 222 public void setDisableRequestAttributeValueStackLookup(String disableRequestAttributeValueStackLookup) { 223 this.disableRequestAttributeValueStackLookup = "true".equalsIgnoreCase(disableRequestAttributeValueStackLookup); 224 } 225 226 /** 227 * Modify state of StrutsConstants.STRUTS_LOCALE setting. 228 * @param val New setting 229 */ 230 @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false) 231 public void setDefaultLocale(String val) { 232 defaultLocale = val; 233 } 234 235 /** 236 * Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting. 237 * @param val New setting 238 */ 239 @Inject(StrutsConstants.STRUTS_I18N_ENCODING) 240 public void setDefaultEncoding(String val) { 241 defaultEncoding = val; 242 } 243 244 /** 245 * Modify state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting. 246 * @param val New setting 247 */ 248 @Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR) 249 public void setMultipartSaveDir(String val) { 250 multipartSaveDir = val; 251 } 252 253 @Inject(StrutsConstants.STRUTS_MULTIPART_PARSER) 254 public void setMultipartHandler(String val) { 255 multipartHandlerName = val; 256 } 257 258 @Inject 259 public void setValueStackFactory(ValueStackFactory valueStackFactory) { 260 this.valueStackFactory = valueStackFactory; 261 } 262 263 @Inject(StrutsConstants.STRUTS_HANDLE_EXCEPTION) 264 public void setHandleException(String handleException) { 265 this.handleException = Boolean.parseBoolean(handleException); 266 } 267 268 @Inject 269 public void setDispatcherErrorHandler(DispatcherErrorHandler errorHandler) { 270 this.errorHandler = errorHandler; 271 } 272 273 /** 274 * Releases all instances bound to this dispatcher instance. 275 */ 276 public void cleanup() { 277 278 // clean up ObjectFactory 279 ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); 280 if (objectFactory == null) { 281 if (LOG.isWarnEnabled()) { 282 LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed"); 283 } 284 } 285 if (objectFactory instanceof ObjectFactoryDestroyable) { 286 try { 287 ((ObjectFactoryDestroyable)objectFactory).destroy(); 288 } 289 catch(Exception e) { 290 // catch any exception that may occurred during destroy() and log it 291 LOG.error("exception occurred while destroying ObjectFactory [#0]", e, objectFactory.toString()); 292 } 293 } 294 295 // clean up Dispatcher itself for this thread 296 instance.set(null); 297 298 // clean up DispatcherListeners 299 if (!dispatcherListeners.isEmpty()) { 300 for (DispatcherListener l : dispatcherListeners) { 301 l.dispatcherDestroyed(this); 302 } 303 } 304 305 // clean up all interceptors by calling their destroy() method 306 Set<Interceptor> interceptors = new HashSet<Interceptor>(); 307 Collection<PackageConfig> packageConfigs = configurationManager.getConfiguration().getPackageConfigs().values(); 308 for (PackageConfig packageConfig : packageConfigs) { 309 for (Object config : packageConfig.getAllInterceptorConfigs().values()) { 310 if (config instanceof InterceptorStackConfig) { 311 for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) { 312 interceptors.add(interceptorMapping.getInterceptor()); 313 } 314 } 315 } 316 } 317 for (Interceptor interceptor : interceptors) { 318 interceptor.destroy(); 319 } 320 321 // Clear container holder when application is unloaded / server shutdown 322 ContainerHolder.clear(); 323 324 //cleanup action context 325 ActionContext.setContext(null); 326 327 // clean up configuration 328 configurationManager.destroyConfiguration(); 329 configurationManager = null; 330 } 331 332 private void init_FileManager() throws ClassNotFoundException { 333 if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER)) { 334 final String fileManagerClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER); 335 final Class<FileManager> fileManagerClass = (Class<FileManager>) Class.forName(fileManagerClassName); 336 if (LOG.isInfoEnabled()) { 337 LOG.info("Custom FileManager specified: #0", fileManagerClassName); 338 } 339 configurationManager.addContainerProvider(new FileManagerProvider(fileManagerClass, fileManagerClass.getSimpleName())); 340 } else { 341 // add any other Struts 2 provided implementations of FileManager 342 configurationManager.addContainerProvider(new FileManagerProvider(JBossFileManager.class, "jboss")); 343 } 344 if (initParams.containsKey(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY)) { 345 final String fileManagerFactoryClassName = initParams.get(StrutsConstants.STRUTS_FILE_MANAGER_FACTORY); 346 final Class<FileManagerFactory> fileManagerFactoryClass = (Class<FileManagerFactory>) Class.forName(fileManagerFactoryClassName); 347 if (LOG.isInfoEnabled()) { 348 LOG.info("Custom FileManagerFactory specified: #0", fileManagerFactoryClassName); 349 } 350 configurationManager.addContainerProvider(new FileManagerFactoryProvider(fileManagerFactoryClass)); 351 } 352 } 353 354 private void init_DefaultProperties() { 355 configurationManager.addContainerProvider(new DefaultPropertiesProvider()); 356 } 357 358 private void init_LegacyStrutsProperties() { 359 configurationManager.addContainerProvider(new PropertiesConfigurationProvider()); 360 } 361 362 private void init_TraditionalXmlConfigurations() { 363 String configPaths = initParams.get("config"); 364 if (configPaths == null) { 365 configPaths = DEFAULT_CONFIGURATION_PATHS; 366 } 367 String[] files = configPaths.split("\\s*[,]\\s*"); 368 for (String file : files) { 369 if (file.endsWith(".xml")) { 370 if ("xwork.xml".equals(file)) { 371 configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false)); 372 } else { 373 configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext)); 374 } 375 } else { 376 throw new IllegalArgumentException("Invalid configuration file name"); 377 } 378 } 379 } 380 381 protected XmlConfigurationProvider createXmlConfigurationProvider(String filename, boolean errorIfMissing) { 382 return new XmlConfigurationProvider(filename, errorIfMissing); 383 } 384 385 protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) { 386 return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx); 387 } 388 389 private void init_CustomConfigurationProviders() { 390 String configProvs = initParams.get("configProviders"); 391 if (configProvs != null) { 392 String[] classes = configProvs.split("\\s*[,]\\s*"); 393 for (String cname : classes) { 394 try { 395 Class cls = ClassLoaderUtil.loadClass(cname, this.getClass()); 396 ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance(); 397 configurationManager.addContainerProvider(prov); 398 } catch (InstantiationException e) { 399 throw new ConfigurationException("Unable to instantiate provider: "+cname, e); 400 } catch (IllegalAccessException e) { 401 throw new ConfigurationException("Unable to access provider: "+cname, e); 402 } catch (ClassNotFoundException e) { 403 throw new ConfigurationException("Unable to locate provider class: "+cname, e); 404 } 405 } 406 } 407 } 408 409 private void init_FilterInitParameters() { 410 configurationManager.addContainerProvider(new ConfigurationProvider() { 411 public void destroy() { 412 } 413 414 public void init(Configuration configuration) throws ConfigurationException { 415 } 416 417 public void loadPackages() throws ConfigurationException { 418 } 419 420 public boolean needsReload() { 421 return false; 422 } 423 424 public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { 425 props.putAll(initParams); 426 } 427 }); 428 } 429 430 private void init_AliasStandardObjects() { 431 configurationManager.addContainerProvider(new DefaultBeanSelectionProvider()); 432 } 433 434 private Container init_PreloadConfiguration() { 435 Container container = getContainer(); 436 437 boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD)); 438 LocalizedTextUtil.setReloadBundles(reloadi18n); 439 440 boolean devMode = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_DEVMODE)); 441 LocalizedTextUtil.setDevMode(devMode); 442 443 return container; 444 } 445 446 private void init_CheckWebLogicWorkaround(Container container) { 447 // test whether param-access workaround needs to be enabled 448 if (servletContext != null && servletContext.getServerInfo() != null 449 && servletContext.getServerInfo().contains("WebLogic")) { 450 if (LOG.isInfoEnabled()) { 451 LOG.info("WebLogic server detected. Enabling Struts parameter access work-around."); 452 } 453 paramsWorkaroundEnabled = true; 454 } else { 455 paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class, 456 StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)); 457 } 458 } 459 460 /** 461 * Load configurations, including both XML and zero-configuration strategies, 462 * and update optional settings, including whether to reload configurations and resource files. 463 */ 464 public void init() { 465 466 if (configurationManager == null) { 467 configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME); 468 } 469 470 try { 471 init_FileManager(); 472 init_DefaultProperties(); // [1] 473 init_TraditionalXmlConfigurations(); // [2] 474 init_LegacyStrutsProperties(); // [3] 475 init_CustomConfigurationProviders(); // [5] 476 init_FilterInitParameters() ; // [6] 477 init_AliasStandardObjects() ; // [7] 478 479 Container container = init_PreloadConfiguration(); 480 container.inject(this); 481 init_CheckWebLogicWorkaround(container); 482 483 if (!dispatcherListeners.isEmpty()) { 484 for (DispatcherListener l : dispatcherListeners) { 485 l.dispatcherInitialized(this); 486 } 487 } 488 errorHandler.init(servletContext); 489 490 } catch (Exception ex) { 491 if (LOG.isErrorEnabled()) 492 LOG.error("Dispatcher initialization failed", ex); 493 throw new StrutsException(ex); 494 } 495 } 496 497 protected ConfigurationManager createConfigurationManager(String name) { 498 return new ConfigurationManager(name); 499 } 500 501 /** 502 * @deprecated use version without ServletContext param 503 */ 504 @Deprecated 505 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, 506 ActionMapping mapping) throws ServletException { 507 508 serviceAction(request, response, mapping); 509 } 510 511 /** 512 * Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result. 513 * <p/> 514 * This method first creates the action context from the given parameters, 515 * and then loads an <tt>ActionProxy</tt> from the given action name and namespace. 516 * After that, the Action method is executed and output channels through the response object. 517 * Actions not found are sent back to the user via the {@link Dispatcher#sendError} method, 518 * using the 404 return code. 519 * All other errors are reported by throwing a ServletException. 520 * 521 * @param request the HttpServletRequest object 522 * @param response the HttpServletResponse object 523 * @param mapping the action mapping object 524 * @throws ServletException when an unknown error occurs (not a 404, but typically something that 525 * would end up as a 5xx by the servlet container) 526 * 527 * @since 2.3.17 528 */ 529 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) 530 throws ServletException { 531 532 Map<String, Object> extraContext = createContextMap(request, response, mapping); 533 534 // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action 535 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); 536 boolean nullStack = stack == null; 537 if (nullStack) { 538 ActionContext ctx = ActionContext.getContext(); 539 if (ctx != null) { 540 stack = ctx.getValueStack(); 541 } 542 } 543 if (stack != null) { 544 extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); 545 } 546 547 String timerKey = "Handling request from Dispatcher"; 548 try { 549 UtilTimerStack.push(timerKey); 550 String namespace = mapping.getNamespace(); 551 String name = mapping.getName(); 552 String method = mapping.getMethod(); 553 554 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( 555 namespace, name, method, extraContext, true, false); 556 557 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); 558 559 // if the ActionMapping says to go straight to a result, do it! 560 if (mapping.getResult() != null) { 561 Result result = mapping.getResult(); 562 result.execute(proxy.getInvocation()); 563 } else { 564 proxy.execute(); 565 } 566 567 // If there was a previous value stack then set it back onto the request 568 if (!nullStack) { 569 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); 570 } 571 } catch (ConfigurationException e) { 572 logConfigurationException(request, e); 573 sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); 574 } catch (Exception e) { 575 if (handleException || devMode) { 576 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); 577 } else { 578 throw new ServletException(e); 579 } 580 } finally { 581 UtilTimerStack.pop(timerKey); 582 } 583 } 584 585 /** 586 * Performs logging of missing action/result configuration exception 587 * 588 * @param request current {@link HttpServletRequest} 589 * @param e {@link ConfigurationException} that occurred 590 */ 591 protected void logConfigurationException(HttpServletRequest request, ConfigurationException e) { 592 // WW-2874 Only log error if in devMode 593 String uri = request.getRequestURI(); 594 if (request.getQueryString() != null) { 595 uri = uri + "?" + request.getQueryString(); 596 } 597 if (devMode) { 598 LOG.error("Could not find action or result\n#0", e, uri); 599 } else if (LOG.isWarnEnabled()) { 600 LOG.warn("Could not find action or result: #0", e, uri); 601 } 602 } 603 604 /** 605 * @deprecated use version without servletContext param 606 */ 607 @Deprecated 608 public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, 609 ActionMapping mapping, ServletContext context) { 610 611 return createContextMap(request, response, mapping); 612 } 613 614 /** 615 * Create a context map containing all the wrapped request objects 616 * 617 * @param request The servlet request 618 * @param response The servlet response 619 * @param mapping The action mapping 620 * @return A map of context objects 621 * 622 * @since 2.3.17 623 */ 624 public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response, 625 ActionMapping mapping) { 626 627 // request map wrapping the http request objects 628 Map requestMap = new RequestMap(request); 629 630 // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately 631 Map params = new HashMap(request.getParameterMap()); 632 633 // session map wrapping the http session 634 Map session = new SessionMap(request); 635 636 // application map wrapping the ServletContext 637 Map application = new ApplicationMap(servletContext); 638 639 Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response); 640 641 if (mapping != null) { 642 extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); 643 } 644 return extraContext; 645 } 646 647 /** 648 * @deprecated use version without ServletContext param 649 */ 650 @Deprecated 651 public HashMap<String,Object> createContextMap(Map requestMap, 652 Map parameterMap, 653 Map sessionMap, 654 Map applicationMap, 655 HttpServletRequest request, 656 HttpServletResponse response, 657 ServletContext servletContext) { 658 659 return createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response); 660 } 661 662 /** 663 * Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire 664 * <tt>Action</tt> context. 665 * 666 * @param requestMap a Map of all request attributes. 667 * @param parameterMap a Map of all request parameters. 668 * @param sessionMap a Map of all session attributes. 669 * @param applicationMap a Map of all servlet context attributes. 670 * @param request the HttpServletRequest object. 671 * @param response the HttpServletResponse object. 672 * @return a HashMap representing the <tt>Action</tt> context. 673 * 674 * @since 2.3.17 675 */ 676 public HashMap<String,Object> createContextMap(Map requestMap, 677 Map parameterMap, 678 Map sessionMap, 679 Map applicationMap, 680 HttpServletRequest request, 681 HttpServletResponse response) { 682 HashMap<String,Object> extraContext = new HashMap<String,Object>(); 683 extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap)); 684 extraContext.put(ActionContext.SESSION, sessionMap); 685 extraContext.put(ActionContext.APPLICATION, applicationMap); 686 687 Locale locale; 688 if (defaultLocale != null) { 689 locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); 690 } else { 691 locale = request.getLocale(); 692 } 693 694 extraContext.put(ActionContext.LOCALE, locale); 695 696 extraContext.put(StrutsStatics.HTTP_REQUEST, request); 697 extraContext.put(StrutsStatics.HTTP_RESPONSE, response); 698 extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext); 699 700 // helpers to get access to request/session/application scope 701 extraContext.put("request", requestMap); 702 extraContext.put("session", sessionMap); 703 extraContext.put("application", applicationMap); 704 extraContext.put("parameters", parameterMap); 705 706 AttributeMap attrMap = new AttributeMap(extraContext); 707 extraContext.put("attr", attrMap); 708 709 return extraContext; 710 } 711 712 /** 713 * Return the path to save uploaded files to (this is configurable). 714 * 715 * @return the path to save uploaded files to 716 */ 717 private String getSaveDir() { 718 String saveDir = multipartSaveDir.trim(); 719 720 if (saveDir.equals("")) { 721 File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir"); 722 if (LOG.isInfoEnabled()) { 723 LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir"); 724 } 725 726 if (tempdir != null) { 727 saveDir = tempdir.toString(); 728 setMultipartSaveDir(saveDir); 729 } 730 } else { 731 File multipartSaveDir = new File(saveDir); 732 733 if (!multipartSaveDir.exists()) { 734 if (!multipartSaveDir.mkdirs()) { 735 String logMessage; 736 try { 737 logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'."; 738 } catch (IOException e) { 739 logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'."; 740 } 741 if (devMode) { 742 LOG.error(logMessage); 743 } else { 744 if (LOG.isWarnEnabled()) { 745 LOG.warn(logMessage); 746 } 747 } 748 } 749 } 750 } 751 752 if (LOG.isDebugEnabled()) { 753 LOG.debug("saveDir=" + saveDir); 754 } 755 756 return saveDir; 757 } 758 759 /** 760 * Prepare a request, including setting the encoding and locale. 761 * 762 * @param request The request 763 * @param response The response 764 */ 765 public void prepare(HttpServletRequest request, HttpServletResponse response) { 766 String encoding = null; 767 if (defaultEncoding != null) { 768 encoding = defaultEncoding; 769 } 770 // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method 771 if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { 772 encoding = "UTF-8"; 773 } 774 775 Locale locale = null; 776 if (defaultLocale != null) { 777 locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); 778 } 779 780 if (encoding != null) { 781 applyEncoding(request, encoding); 782 } 783 784 if (locale != null) { 785 response.setLocale(locale); 786 } 787 788 if (paramsWorkaroundEnabled) { 789 request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request 790 } 791 } 792 793 private void applyEncoding(HttpServletRequest request, String encoding) { 794 try { 795 if (!encoding.equals(request.getCharacterEncoding())) { 796 // if the encoding is already correctly set and the parameters have been already read 797 // do not try to set encoding because it is useless and will cause an error 798 request.setCharacterEncoding(encoding); 799 } 800 } catch (Exception e) { 801 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); 802 } 803 } 804 805 /** 806 * @deprecated use version without ServletContext param 807 */ 808 @Deprecated 809 public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { 810 return wrapRequest(request); 811 } 812 813 /** 814 * Wrap and return the given request or return the original request object. 815 * </p> 816 * This method transparently handles multipart data as a wrapped class around the given request. 817 * Override this method to handle multipart requests in a special way or to handle other types of requests. 818 * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is 819 * flexible - look first to that object before overriding this method to handle multipart data. 820 * 821 * @param request the HttpServletRequest object. 822 * @return a wrapped request or original request. 823 * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper 824 * @throws java.io.IOException on any error. 825 * 826 * @since 2.3.17 827 */ 828 public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { 829 // don't wrap more than once 830 if (request instanceof StrutsRequestWrapper) { 831 return request; 832 } 833 834 String content_type = request.getContentType(); 835 if (content_type != null && content_type.contains("multipart/form-data")) { 836 MultiPartRequest mpr = getMultiPartRequest(); 837 LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); 838 request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); 839 } else { 840 request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); 841 } 842 843 return request; 844 } 845 846 /** 847 * On each request it must return a new instance as implementation could be not thread safe 848 * and thus ensure of resource clean up 849 * 850 * @return 851 */ 852 protected MultiPartRequest getMultiPartRequest() { 853 MultiPartRequest mpr = null; 854 //check for alternate implementations of MultiPartRequest 855 Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); 856 for (String multiName : multiNames) { 857 if (multiName.equals(multipartHandlerName)) { 858 mpr = getContainer().getInstance(MultiPartRequest.class, multiName); 859 } 860 } 861 if (mpr == null ) { 862 mpr = getContainer().getInstance(MultiPartRequest.class); 863 } 864 return mpr; 865 } 866 867 /** 868 * Removes all the files created by MultiPartRequestWrapper. 869 * 870 * @param request the HttpServletRequest object. 871 * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper 872 */ 873 public void cleanUpRequest(HttpServletRequest request) { 874 ContainerHolder.clear(); 875 if (!(request instanceof MultiPartRequestWrapper)) { 876 return; 877 } 878 MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; 879 multiWrapper.cleanUp(); 880 } 881 882 /** 883 * Send an HTTP error response code. 884 * 885 * @param request the HttpServletRequest object. 886 * @param response the HttpServletResponse object. 887 * @param code the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes). 888 * @param e the Exception that is reported. 889 * @param ctx the ServletContext object. 890 * 891 * @deprecated remove in version 3.0 - use version without ServletContext parameter 892 */ 893 @Deprecated 894 public void sendError(HttpServletRequest request, HttpServletResponse response, ServletContext ctx, int code, Exception e) { 895 sendError(request, response, code, e); 896 } 897 898 /** 899 * Send an HTTP error response code. 900 * 901 * @param request the HttpServletRequest object. 902 * @param response the HttpServletResponse object. 903 * @param code the HttpServletResponse error code (see {@link javax.servlet.http.HttpServletResponse} for possible error codes). 904 * @param e the Exception that is reported. 905 * 906 * @since 2.3.17 907 */ 908 public void sendError(HttpServletRequest request, HttpServletResponse response, int code, Exception e) { 909 errorHandler.handleError(request, response, code, e); 910 } 911 912 /** 913 * Cleanup any resources used to initialise Dispatcher 914 */ 915 public void cleanUpAfterInit() { 916 if (LOG.isDebugEnabled()) { 917 LOG.debug("Cleaning up resources used to init Dispatcher"); 918 } 919 ContainerHolder.clear(); 920 } 921 922 /** 923 * Provide an accessor class for static XWork utility. 924 */ 925 public static class Locator { 926 public Location getLocation(Object obj) { 927 Location loc = LocationUtils.getLocation(obj); 928 if (loc == null) { 929 return Location.UNKNOWN; 930 } 931 return loc; 932 } 933 } 934 935 /** 936 * Expose the ConfigurationManager instance. 937 * 938 * @return The instance 939 */ 940 public ConfigurationManager getConfigurationManager() { 941 return configurationManager; 942 } 943 944 /** 945 * Modify the ConfigurationManager instance 946 * 947 * @param mgr The configuration manager 948 * @deprecated should be removed as is used only in tests 949 */ 950 public void setConfigurationManager(ConfigurationManager mgr) { 951 ContainerHolder.clear(); 952 this.configurationManager = mgr; 953 } 954 955 /** 956 * Expose the dependency injection container. 957 * @return Our dependency injection container 958 */ 959 public Container getContainer() { 960 if (ContainerHolder.get() != null) { 961 return ContainerHolder.get(); 962 } 963 ConfigurationManager mgr = getConfigurationManager(); 964 if (mgr == null) { 965 throw new IllegalStateException("The configuration manager shouldn't be null"); 966 } else { 967 Configuration config = mgr.getConfiguration(); 968 if (config == null) { 969 throw new IllegalStateException("Unable to load configuration"); 970 } else { 971 Container container = config.getContainer(); 972 ContainerHolder.store(container); 973 return container; 974 } 975 } 976 } 977 978 }
上面说到FilterDispatcher利用ActionMapper匹配请求路径成功后便交给Dispatcher.serviceAtion(request, response, mapping)处理,其中mapping包含里被匹配成功的Action中所有的信息,所以可以理解为一个action配置的对象,下面分析Dispatcher.service是如何处理的。
1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) 2 throws ServletException { 3 4 Map<String, Object> extraContext = createContextMap(request, response, mapping); 5 6 // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action 7 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); 8 boolean nullStack = stack == null; 9 if (nullStack) { 10 ActionContext ctx = ActionContext.getContext(); 11 if (ctx != null) { 12 stack = ctx.getValueStack(); 13 } 14 } 15 if (stack != null) { 16 extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); 17 } 18 19 String timerKey = "Handling request from Dispatcher"; 20 try { 21 UtilTimerStack.push(timerKey); 22 String namespace = mapping.getNamespace(); 23 String name = mapping.getName(); 24 String method = mapping.getMethod(); 25 26 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( 27 namespace, name, method, extraContext, true, false); 28 29 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); 30 31 // if the ActionMapping says to go straight to a result, do it! 32 if (mapping.getResult() != null) { 33 Result result = mapping.getResult(); 34 result.execute(proxy.getInvocation()); 35 } else { 36 proxy.execute(); 37 } 38 39 // If there was a previous value stack then set it back onto the request 40 if (!nullStack) { 41 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); 42 } 43 } catch (ConfigurationException e) { 44 logConfigurationException(request, e); 45 sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); 46 } catch (Exception e) { 47 if (handleException || devMode) { 48 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); 49 } else { 50 throw new ServletException(e); 51 } 52 } finally { 53 UtilTimerStack.pop(timerKey); 54 } 55 }
方法中Dispatcher先利用匹配出的mapping获得action对象的命名空间(nameSpace),actionName,method(22,23,24行),再根据这些信息创建代理对象proxy(26,27行),并执行excute(36行)方法。
以上是struts2的FilterDsipatcher的部分执行过程,可以作如下总结:

浙公网安备 33010602011771号