Struts2 两大运行主线:
  • 1.初始化主线:初始化主线主要是为Struts2创建运行环境(此处的环境与Struts2身处的Web环境是有区别的),初始化入口StrutsPrepareAndExecuteFilter继承 Filter,遵循Filter规范,初始化只是在应用启动的时候运行一次,以后无论过来多少HttpServletRequest都不会再运行啦。

StrutsPrepareAndExecuteFilter.java

  1. public void init(FilterConfig filterConfig) throws ServletException {
  2. InitOperations init = new InitOperations();//Http请求预处理工具类,ExecuteOperates  Http请求逻辑处理类
  3. Dispatcher dispatcher = null;
  4. try {
  5. FilterHostConfig config = new FilterHostConfig(filterConfig);
  6. init.initLogging(config);
  7. dispatcher = init.initDispatcher(config);//核心分发器Dispatcher初始化
  8. //初始化静态资源加载器
  9. init.initStaticContentLoader(config, dispatcher);
  10. //初始化进行http预处理操作类
  11. prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
  12. //初始化进行http请求逻辑处理操作类
  13. execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
  14. this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
  15. postInit(dispatcher, filterConfig);
  16. } finally {
  17. if (dispatcher != null) {
  18. dispatcher.cleanUpAfterInit();
  19. }
  20. init.cleanup();//初始化清除工作
  21. }
  22. }

首先我们来看看,Struts2核心分发器初始化过程:

InitOperations.java

  1. /**
  2. * Creates and initializes the dispatcher
  3. */
  4. public Dispatcher initDispatcher( HostConfig filterConfig ) {
  5. Dispatcher dispatcher = createDispatcher(filterConfig);
  6. dispatcher.init();
  7. return dispatcher;
  8. }
  9. /**
  10. * Create a {@link Dispatcher}
  11. */
  12. private Dispatcher createDispatcher( HostConfig filterConfig ) {
  13. Map<String, String> params = new HashMap<String, String>();
  14. for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
  15. String name = (String) e.next();
  16. String value = filterConfig.getInitParameter(name);
  17. params.put(name, value);
  18. }
  19. return new Dispatcher(filterConfig.getServletContext(), params);
  20. }

顺着流程,接着探索dispatcher.init(),这里是加载配置文件的地方

Dispatcher.java

  1. public void init() {
  2. if (configurationManager == null) {
  3. configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
  4. }
  5. try {
  6. init_FileManager();
  7. init_DefaultProperties(); // [1]加载default.properties org/apache/struts2/default.properties
  8. init_TraditionalXmlConfigurations(); // [2]加载struts-default.xml,struts-plugin.xml,struts.xml
  9. init_LegacyStrutsProperties(); // [3]
  10. init_CustomConfigurationProviders(); // [5]初始化自定义的provider配置类全名和实现ConfigurationProvider接口,用逗号隔开即可
  11. init_FilterInitParameters() ; // [6]
  12. init_AliasStandardObjects() ; // [7]加载框架内部内置的对象struts-default.xml中<bean>节点
  13. Container container = init_PreloadConfiguration();
  14. container.inject(this);
  15. init_CheckWebLogicWorkaround(container);
  16. if (!dispatcherListeners.isEmpty()) {
  17. for (DispatcherListener l : dispatcherListeners) {
  18. l.dispatcherInitialized(this);
  19. }
  20. }
  21. } catch (Exception ex) {
  22. if (LOG.isErrorEnabled())
  23. LOG.error("Dispatcher initialization failed", ex);
  24. throw new StrutsException(ex);
  25. }
  26. }

让我们截取init_TraditionalXmlConfigurations();的加载过程来了解Struts2是如何来加载我们配置的配置文件的:

  1. private void init_TraditionalXmlConfigurations() {
  2. //首先读取web.xml中的config初始参数 如果不存在,默认加载加载struts-default.xml,struts-plugin.xml,struts.xml
  3. //如果不想使用默认值,在web.xml中设置config参数即可
  4. String configPaths = initParams.get("config");
  5. if (configPaths == null) {
  6. configPaths = DEFAULT_CONFIGURATION_PATHS;
  7. }
  8. String[] files = configPaths.split("\\s*[,]\\s*");
  9. for (String file : files) {
  10. if (file.endsWith(".xml")) {
  11. if ("xwork.xml".equals(file)) {//xwork.xml文件单独解析
  12. configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false));
  13. } else {
  14. configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
  15. }
  16. } else {
  17. throw new IllegalArgumentException("Invalid configuration file name");
  18. }
  19. }
  20. }

createStrutsXmlConfigurationProvider方法创建StrutsXmlConfigurationProvider对象,其继承XmlConfigurationProvider对象,而XmlConfigurationProvider 实现了ConfigurationProvider接口,ConfigurationProvider使用java很少见到的多继承机制,继承了ContainerProvider和PackageProvider接口。 XmlConfigurationProvider负责读取和解析配置文件,

  1. /**
  2. * Create a PackageConfig from an XML element representing it.
  3. */
  4. protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
  5. String packageName = packageElement.getAttribute("name");
  6. PackageConfig packageConfig = configuration.getPackageConfig(packageName);
  7. if (packageConfig != null) {
  8. if (LOG.isDebugEnabled()) {
  9. LOG.debug("Package [#0] already loaded, skipping re-loading it and using existing PackageConfig [#1]", packageName, packageConfig);
  10. }
  11. return packageConfig;
  12. }
  13. PackageConfig.Builder newPackage = buildPackageContext(packageElement);
  14. if (newPackage.isNeedsRefresh()) {
  15. return newPackage.build();
  16. }
  17. if (LOG.isDebugEnabled()) {
  18. LOG.debug("Loaded " + newPackage);
  19. }
  20. // add result types (and default result) to this package
  21. addResultTypes(newPackage, packageElement);
  22. // load the interceptors and interceptor stacks for this package
  23. loadInterceptors(newPackage, packageElement);
  24. // load the default interceptor reference for this package
  25. loadDefaultInterceptorRef(newPackage, packageElement);
  26. // load the default class ref for this package
  27. loadDefaultClassRef(newPackage, packageElement);
  28. // load the global result list for this package
  29. loadGlobalResults(newPackage, packageElement);
  30. // load the global exception handler list for this package
  31. loadGobalExceptionMappings(newPackage, packageElement);
  32. // get actions
  33. NodeList actionList = packageElement.getElementsByTagName("action");
  34. for (int i = 0; i < actionList.getLength(); i++) {
  35. Element actionElement = (Element) actionList.item(i);
  36. addAction(actionElement, newPackage);
  37. }
  38. // load the default action reference for this package
  39. loadDefaultActionRef(newPackage, packageElement);
  40. PackageConfig cfg = newPackage.build();
  41. configuration.addPackageConfig(cfg.getName(), cfg);
  42. return cfg;
  43. }

其中

  • addAction()方法负责读取节点,并将数据保存在ActionConfig中;
  • addResultTypes()方法负责读取节点,并将数据保存在ResultTypeConfig中;ResultTypeConfig使用了构造器模式创建对象
  • loadInterceptors()方法负责读取节点,并将数据保存在InterceptorConfig中;
  • loadInterceptorStack()方法负责读取节点,并将数据保存在InterceptorStackConfig中;
  • loadInterceptorStacks()方法负责读取节点,并将数据保存在InterceptorStackConfig中;

而以上这些方法都将会被addPackage()invoke,并将数据汇集到PackageConfig中。

配置文件实际的加载流程: DefaultConfiguration.reloadContainer()时调用了containerProvider.init(this);

XmlConfigurationProvider.java

  1. public void init(Configuration configuration) {
  2. this.configuration = configuration;
  3. this.includedFileNames = configuration.getLoadedFileNames();
  4. loadDocuments(configFileName);
  5. }

加载Documents:

  1. private void loadDocuments(String configFileName) {
  2. try {
  3. loadedFileUrls.clear();
  4. documents = loadConfigurationFiles(configFileName, null);
  5. } catch (ConfigurationException e) {
  6. throw e;
  7. } catch (Exception e) {
  8. throw new ConfigurationException("Error loading configuration file " + configFileName, e);
  9. }
  10. }
  11. private List<Document> loadConfigurationFiles(String fileName, Element includeElement) {
  12. List<Document> docs = new ArrayList<Document>();
  13. List<Document> finalDocs = new ArrayList<Document>();
  14. if (!includedFileNames.contains(fileName)) {
  15. if (LOG.isDebugEnabled()) {
  16. LOG.debug("Loading action configurations from: " + fileName);
  17. }
  18. includedFileNames.add(fileName);
  19. Iterator<URL> urls = null;
  20. InputStream is = null;
  21. IOException ioException = null;
  22. try {
  23. urls = getConfigurationUrls(fileName);
  24. } catch (IOException ex) {
  25. ioException = ex;
  26. }
  27. if (urls == null || !urls.hasNext()) {
  28. if (errorIfMissing) {
  29. throw new ConfigurationException("Could not open files of the name " + fileName, ioException);
  30. } else {
  31. if (LOG.isInfoEnabled()) {
  32. LOG.info("Unable to locate configuration files of the name "
  33. + fileName + ", skipping");
  34. }
  35. return docs;
  36. }
  37. }
  38. URL url = null;
  39. while (urls.hasNext()) {
  40. try {
  41. url = urls.next();
  42. is = fileManager.loadFile(url);
  43. InputSource in = new InputSource(is);
  44. in.setSystemId(url.toString());
  45. docs.add(DomHelper.parse(in, dtdMappings));
  46. } catch (XWorkException e) {
  47. if (includeElement != null) {
  48. throw new ConfigurationException("Unable to load " + url, e, includeElement);
  49. } else {
  50. throw new ConfigurationException("Unable to load " + url, e);
  51. }
  52. } catch (Exception e) {
  53. throw new ConfigurationException("Caught exception while loading file " + fileName, e, includeElement);
  54. } finally {
  55. if (is != null) {
  56. try {
  57. is.close();
  58. } catch (IOException e) {
  59. LOG.error("Unable to close input stream", e);
  60. }
  61. }
  62. }
  63. }
  64. //sort the documents, according to the "order" attribute
  65. Collections.sort(docs, new Comparator<Document>() {
  66. public int compare(Document doc1, Document doc2) {
  67. return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2));
  68. }
  69. });
  70. for (Document doc : docs) {
  71. Element rootElement = doc.getDocumentElement();
  72. NodeList children = rootElement.getChildNodes();
  73. int childSize = children.getLength();
  74. for (int i = 0; i < childSize; i++) {
  75. Node childNode = children.item(i);
  76. if (childNode instanceof Element) {
  77. Element child = (Element) childNode;
  78. final String nodeName = child.getNodeName();
  79. //解析每个action配置是,对于include文件可以使用通配符*来进行配置
  80. //如Struts.xml中可配置成<include file="actions_*.xml"/>
  81. if ("include".equals(nodeName)) {
  82. String includeFileName = child.getAttribute("file");
  83. if (includeFileName.indexOf('*') != -1) {
  84. // handleWildCardIncludes(includeFileName, docs, child);
  85. ClassPathFinder wildcardFinder = new ClassPathFinder();
  86. wildcardFinder.setPattern(includeFileName);
  87. Vector<String> wildcardMatches = wildcardFinder.findMatches();
  88. for (String match : wildcardMatches) {
  89. finalDocs.addAll(loadConfigurationFiles(match, child));
  90. }
  91. } else {
  92. finalDocs.addAll(loadConfigurationFiles(includeFileName, child));
  93. }
  94. }
  95. }
  96. }
  97. finalDocs.add(doc);
  98. loadedFileUrls.add(url.toString());
  99. }
  100. if (LOG.isDebugEnabled()) {
  101. LOG.debug("Loaded action configuration from: " + fileName);
  102. }
  103. }
  104. return finalDocs;
  105. }

init_CustomConfigurationProviders(); // [5]初始化自定义的provider配置类全名和实现ConfigurationProvider接口,用逗号隔开即可

  1. private void init_CustomConfigurationProviders() {
  2. String configProvs = initParams.get("configProviders");
  3. if (configProvs != null) {
  4. String[] classes = configProvs.split("\\s*[,]\\s*");
  5. for (String cname : classes) {
  6. try {
  7. Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
  8. ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
  9. configurationManager.addContainerProvider(prov);
  10. } catch (InstantiationException e) {
  11. throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
  12. } catch (IllegalAccessException e) {
  13. throw new ConfigurationException("Unable to access provider: "+cname, e);
  14. } catch (ClassNotFoundException e) {
  15. throw new ConfigurationException("Unable to locate provider class: "+cname, e);
  16. }
  17. }
  18. }
  19. }

接下来我们把目光放到Container container = init_PreloadConfiguration();上,从代码的表面意义上可以看出是对容器进行初始化

  1. private Container init_PreloadConfiguration() {
  2. Configuration config = configurationManager.getConfiguration();
  3. Container container = config.getContainer();
  4. boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));
  5. LocalizedTextUtil.setReloadBundles(reloadi18n);
  6. ContainerHolder.store(container);
  7. return container;
  8. }

Configuration与ConfigurationManager作为Struts2初始化过程中的两大强力的辅助类,对于配置元素的管理启动了至关重要的作用。

Configuration,提供了框架所有配置元素访问的接口,而且对所有配置元素进行初始化调度,接下来我们就从 Configuration config = configurationManager.getConfiguration();一点一点地来揭示Configuration对配置元素初始化调度的本质。

  1. /**
  2. * Get the current XWork configuration object.  By default an instance of DefaultConfiguration will be returned
  3. *
  4. * @see com.opensymphony.xwork2.config.impl.DefaultConfiguration
  5. */
  6. public synchronized Configuration getConfiguration() {
  7. if (configuration == null) {
  8. setConfiguration(createConfiguration(defaultFrameworkBeanName));
  9. try {
  10. configuration.reloadContainer(getContainerProviders());
  11. } catch (ConfigurationException e) {
  12. setConfiguration(null);
  13. throw new ConfigurationException("Unable to load configuration.", e);
  14. }
  15. } else {
  16. conditionalReload();
  17. }
  18. return configuration;
  19. }

关键在于configuration.reloadContainer(getContainerProviders());方法,我们接着看代码:

  1. /**
  2. * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
  3. * buildRuntimeConfiguration().
  4. *
  5. * @throws ConfigurationException
  6. */
  7. public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
  8. packageContexts.clear();
  9. loadedFileNames.clear();
  10. List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();
  11. ContainerProperties props = new ContainerProperties();
  12. ContainerBuilder builder = new ContainerBuilder();
  13. Container bootstrap = createBootstrapContainer(providers);
  14. for (final ContainerProvider containerProvider : providers)
  15. {
  16. bootstrap.inject(containerProvider);
  17. containerProvider.init(this);
  18. containerProvider.register(builder, props);
  19. }
  20. props.setConstants(builder);
  21. builder.factory(Configuration.class, new Factory<Configuration>() {
  22. public Configuration create(Context context) throws Exception {
  23. return DefaultConfiguration.this;
  24. }
  25. });
  26. ActionContext oldContext = ActionContext.getContext();
  27. try {
  28. // Set the bootstrap container for the purposes of factory creation
  29. setContext(bootstrap);
  30. container = builder.create(false);
  31. setContext(container);
  32. objectFactory = container.getInstance(ObjectFactory.class);
  33. // Process the configuration providers first
  34. for (final ContainerProvider containerProvider : providers)
  35. {
  36. if (containerProvider instanceof PackageProvider) {
  37. container.inject(containerProvider);
  38. ((PackageProvider)containerProvider).loadPackages();
  39. packageProviders.add((PackageProvider)containerProvider);
  40. }
  41. }
  42. // Then process any package providers from the plugins
  43. Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
  44. for (String name : packageProviderNames) {
  45. PackageProvider provider = container.getInstance(PackageProvider.class, name);
  46. provider.init(this);
  47. provider.loadPackages();
  48. packageProviders.add(provider);
  49. }
  50. rebuildRuntimeConfiguration();
  51. } finally {
  52. if (oldContext == null) {
  53. ActionContext.setContext(null);
  54. }
  55. }
  56. return packageProviders;
  57. }

很显然我们在这个方法中看到,方法的参数即是一组配置元素的加载器(ConfigurationProvider) Struts2的初始换主线自此全部结束。

*****************************************华丽的分割线***************************************

下面我们再来看看Struts2的第二条主线:

######请求处理主线 我们回到StrutsPrepareAndExecuteFilter来看看,Filter标准中的doFilter()方法:

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  2. HttpServletRequest request = (HttpServletRequest) req;
  3. HttpServletResponse response = (HttpServletResponse) res;
  4. try {
  5. prepare.setEncodingAndLocale(request, response);
  6. prepare.createActionContext(request, response);
  7. prepare.assignDispatcherToThread();
  8. if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
  9. chain.doFilter(request, response);
  10. } else {
  11. request = prepare.wrapRequest(request);
  12. ActionMapping mapping = prepare.findActionMapping(request, response, true);
  13. if (mapping == null) {
  14. boolean handled = execute.executeStaticResourceRequest(request, response);
  15. if (!handled) {
  16. chain.doFilter(request, response);
  17. }
  18. } else {
  19. execute.executeAction(request, response, mapping);
  20. }
  21. }
  22. } finally {
  23. prepare.cleanupRequest(request);
  24. }
  25. }

prepare.createActionContext(request, response);:创建上下文ActionContext并初始化其内部线程安全的ThreadLocal对象

  1. /**
  2. * Creates the action context and initializes the thread local
  3. */
  4. public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
  5. ActionContext ctx;
  6. Integer counter = 1;
  7. Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
  8. if (oldCounter != null) {
  9. counter = oldCounter + 1;
  10. }
  11. ActionContext oldContext = ActionContext.getContext();
  12. if (oldContext != null) {
  13. // detected existing context, so we are probably in a forward
  14. ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
  15. } else {
  16. ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
  17. stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
  18. ctx = new ActionContext(stack.getContext());
  19. }
  20. request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
  21. ActionContext.setContext(ctx);
  22. return ctx;
  23. }

prepare.assignDispatcherToThread();:将dispatcher对象绑定到Dispatcher内部线程安全的instance对象中

request = prepare.wrapRequest(request);:对HttpServletRequest进行封装,传统的Web容器元素的数据是通过HttpServletRequest 的接口实现访问,但是在Struts2中数据的储存位置发生了变化,它们不在适合于web对象绑定在一起,而是以ActionContext的形式存在于当前 的线程中,故传统的访问方式无法访问到Struts2中的数据,因此Struts2针对这种情况作出了对HttpServletRequest装饰的扩展。

PrepareOperations.java

  1. /**
  2. * Wraps the request with the Struts wrapper that handles multipart requests better
  3. * @return The new request, if there is one
  4. * @throws ServletException
  5. */
  6. public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
  7. HttpServletRequest request = oldRequest;
  8. try {
  9. // Wrap request first, just in case it is multipart/form-data
  10. // parameters might not be accessible through before encoding (ww-1278)
  11. request = dispatcher.wrapRequest(request, servletContext);
  12. } catch (IOException e) {
  13. throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
  14. }
  15. return request;
  16. }

Dispatcher.java

  1. /**
  2. * Wrap and return the given request or return the original request object.
  3. * </p>
  4. * This method transparently handles multipart data as a wrapped class around the given request.
  5. * Override this method to handle multipart requests in a special way or to handle other types of requests.
  6. * Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
  7. * flexible - look first to that object before overriding this method to handle multipart data.
  8. *
  9. * @param request the HttpServletRequest object.
  10. * @param servletContext Our ServletContext object
  11. * @return a wrapped request or original request.
  12. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
  13. * @throws java.io.IOException on any error.
  14. */
  15. public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
  16. // don't wrap more than once
  17. if (request instanceof StrutsRequestWrapper) {
  18. return request;
  19. }
  20. String content_type = request.getContentType();
  21. if (content_type != null && content_type.contains("multipart/form-data")) {
  22. MultiPartRequest mpr = getMultiPartRequest();
  23. LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
  24. request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
  25. } else {
  26. request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
  27. }
  28. return request;
  29. }

ActionMapping mapping = prepare.findActionMapping(request, response, true);ActionMapping是一个普通的java类,但是它将 URL形式的HTTP请求与Struts2中的Action建立起了联系。Struts2在进行Http请求处理时,由ActionMapper的实现类在运行期查找相应的 事件映射关系并生成ActionMapping对象。

  1. /**
  2. * Finds and optionally creates an {@link ActionMapping}.  if forceLookup is false, it first looks in the current request to see if one
  3. * has already been found, otherwise, it creates it and stores it in the request.  No mapping will be created in the
  4. * case of static resource requests or unidentifiable requests for other servlets, for example.
  5. * @param forceLookup if true, the action mapping will be looked up from the ActionMapper instance, ignoring if there is one
  6. * in the request or not
  7. */
  8. public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
  9. ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
  10. if (mapping == null || forceLookup) {
  11. try {
  12. //ActionMapper类才是Struts2进行URL Mapping关系查找的核心类
  13. mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
  14. if (mapping != null) {
  15. request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
  16. }
  17. } catch (Exception ex) {
  18. dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
  19. }
  20. }
  21. return mapping;
  22. }

ActionMapper类具有多个默认的实现类,每个实现类具有不同的ActionMapping查找规则,所以这个地方给我们留下了无限的遐想,是个很好的扩展点。

execute.executeAction(request, response, mapping);:开始真正执行业务逻辑 ExecuteOperations.java

  1. /**
  2. * Executes an action
  3. * @throws ServletException
  4. */
  5. public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
  6. dispatcher.serviceAction(request, response, servletContext, mapping);
  7. }

Dispatcher.java

  1. public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
  2. ActionMapping mapping) throws ServletException {
  3. Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
  4. // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
  5. ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
  6. boolean nullStack = stack == null;
  7. if (nullStack) {
  8. ActionContext ctx = ActionContext.getContext();
  9. if (ctx != null) {
  10. stack = ctx.getValueStack();
  11. }
  12. }
  13. if (stack != null) {
  14. extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
  15. }
  16. String timerKey = "Handling request from Dispatcher";
  17. try {
  18. UtilTimerStack.push(timerKey);
  19. String namespace = mapping.getNamespace();
  20. String name = mapping.getName();
  21. String method = mapping.getMethod();
  22. Configuration config = configurationManager.getConfiguration();
  23. ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
  24. namespace, name, method, extraContext, true, false);
  25. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
  26. //如果ActionMapping对象中包含Result对象,则直接跳过Action而执行Result
  27. // if the ActionMapping says to go straight to a result, do it!
  28. if (mapping.getResult() != null) {
  29. Result result = mapping.getResult();
  30. result.execute(proxy.getInvocation());
  31. } else {
  32. proxy.execute();
  33. }
  34. // If there was a previous value stack then set it back onto the request
  35. if (!nullStack) {
  36. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
  37. }
  38. } catch (ConfigurationException e) {
  39. // WW-2874 Only log error if in devMode
  40. if (devMode) {
  41. String reqStr = request.getRequestURI();
  42. if (request.getQueryString() != null) {
  43. reqStr = reqStr + "?" + request.getQueryString();
  44. }
  45. LOG.error("Could not find action or result\n" + reqStr, e);
  46. } else {
  47. if (LOG.isWarnEnabled()) {
  48. LOG.warn("Could not find action or result", e);
  49. }
  50. }
  51. sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
  52. } catch (Exception e) {
  53. if (handleException || devMode) {
  54. sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
  55. } else {
  56. throw new ServletException(e);
  57. }
  58. } finally {
  59. UtilTimerStack.pop(timerKey);
  60. }
  61. }

Map<String, Object> extraContext = createContextMap(request, response, mapping, context);: 该方法主要把Application、Session、Request的key value值拷贝到Map中,并放在HashMap<String,Object>中

Dispatcher.java

  1. /**
  2. * Create a context map containing all the wrapped request objects
  3. *
  4. * @param request The servlet request
  5. * @param response The servlet response
  6. * @param mapping The action mapping
  7. * @param context The servlet context
  8. * @return A map of context objects
  9. */
  10. public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
  11. ActionMapping mapping, ServletContext context) {
  12. // request map wrapping the http request objects
  13. Map requestMap = new RequestMap(request);
  14. // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
  15. Map params = new HashMap(request.getParameterMap());
  16. // session map wrapping the http session
  17. Map session = new SessionMap(request);
  18. // application map wrapping the ServletContext
  19. Map application = new ApplicationMap(context);
  20. Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
  21. if (mapping != null) {
  22. extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
  23. }
  24. return extraContext;
  25. }

接下来调用Configuration对象的reloadContainer()利用ContainerBuilder对象生成Container对象,为生成ActionProxy对象做好准备,

ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false);创建ActionProxyFacotry的过程也完成了ActionInvocation对象的创建:

DefaultActionProxyFactory.java----------createActionProxy()

  1. public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
  2. DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
  3. container.inject(proxy);
  4. proxy.prepare();//
  5. return proxy;
  6. }

DefaultActionProxy.java----------prepare()

  1. protected void prepare() {
  2. String profileKey = "create DefaultActionProxy: ";
  3. try {
  4. UtilTimerStack.push(profileKey);
  5. config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
  6. if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
  7. config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
  8. }
  9. if (config == null) {
  10. throw new ConfigurationException(getErrorMessage());
  11. }
  12. resolveMethod();
  13. if (!config.isAllowedMethod(method)) {
  14. throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
  15. }
  16. invocation.init(this);
  17. } finally {
  18. UtilTimerStack.pop(profileKey);
  19. }
  20. }

DefaultActionInvocation.java-------------init(ActionProxy proxy)

  1. public void init(ActionProxy proxy) {
  2. this.proxy = proxy;
  3. Map<String, Object> contextMap = createContextMap();
  4. // Setting this so that other classes, like object factories, can use the ActionProxy and other
  5. // contextual information to operate
  6. ActionContext actionContext = ActionContext.getContext();
  7. if (actionContext != null) {
  8. actionContext.setActionInvocation(this);
  9. }
  10. createAction(contextMap);
  11. if (pushAction) {
  12. stack.push(action);//压入CompoundRoot 压栈操作
  13. contextMap.put("action", action);
  14. }
  15. invocationContext = new ActionContext(contextMap);
  16. invocationContext.setName(proxy.getActionName());
  17. // get a new List so we don't get problems with the iterator if someone changes the list
  18. List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
  19. interceptors = interceptorList.iterator();
  20. }

DefaultActionInvocation.java---createAction(contextMap);

  1. protected void createAction(Map<String, Object> contextMap) {
  2. // load action
  3. String timerKey = "actionCreate: " + proxy.getActionName();
  4. try {
  5. UtilTimerStack.push(timerKey);
  6. action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
  7. } catch (InstantiationException e) {
  8. throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
  9. } catch (IllegalAccessException e) {
  10. throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
  11. } catch (Exception e) {
  12. String gripe;
  13. if (proxy == null) {
  14. gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
  15. } else if (proxy.getConfig() == null) {
  16. gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
  17. } else if (proxy.getConfig().getClassName() == null) {
  18. gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
  19. } else {
  20. gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
  21. }
  22. gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
  23. throw new XWorkException(gripe, e, proxy.getConfig());
  24. } finally {
  25. UtilTimerStack.pop(timerKey);
  26. }
  27. if (actionEventListener != null) {
  28. action = actionEventListener.prepare(action, stack);
  29. }
  30. }

继续回到Dispatcher中的proxy.execute();继续执行业务逻辑 DefaultActionProxy.java-------------execute()

  1. public String execute() throws Exception {
  2. ActionContext nestedContext = ActionContext.getContext();
  3. ActionContext.setContext(invocation.getInvocationContext());
  4. String retCode = null;
  5. String profileKey = "execute: ";
  6. try {
  7. UtilTimerStack.push(profileKey);
  8. retCode = invocation.invoke();
  9. } finally {
  10. if (cleanupContext) {
  11. ActionContext.setContext(nestedContext);
  12. }
  13. UtilTimerStack.pop(profileKey);
  14. }
  15. return retCode;
  16. }

DefaultActionInvocation.java---invoke()

  1. /**
  2. * @throws ConfigurationException If no result can be found with the returned code
  3. */
  4. public String invoke() throws Exception {
  5. String profileKey = "invoke: ";
  6. try {
  7. UtilTimerStack.push(profileKey);
  8. if (executed) {
  9. throw new IllegalStateException("Action has already executed");
  10. }
  11. if (interceptors.hasNext()) {
  12. final InterceptorMapping interceptor = interceptors.next();
  13. String interceptorMsg = "interceptor: " + interceptor.getName();
  14. UtilTimerStack.push(interceptorMsg);
  15. try {
  16. resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
  17. }
  18. finally {
  19. UtilTimerStack.pop(interceptorMsg);
  20. }
  21. } else {
  22. resultCode = invokeActionOnly();
  23. }
  24. // this is needed because the result will be executed, then control will return to the Interceptor, which will
  25. // return above and flow through again
  26. if (!executed) {
  27. if (preResultListeners != null) {
  28. for (Object preResultListener : preResultListeners) {
  29. PreResultListener listener = (PreResultListener) preResultListener;
  30. String _profileKey = "preResultListener: ";
  31. try {
  32. UtilTimerStack.push(_profileKey);
  33. listener.beforeResult(this, resultCode);
  34. }
  35. finally {
  36. UtilTimerStack.pop(_profileKey);
  37. }
  38. }
  39. }
  40. // now execute the result, if we're supposed to
  41. if (proxy.getExecuteResult()) {
  42. executeResult();
  43. }
  44. executed = true;
  45. }
  46. return resultCode;
  47. }
  48. finally {
  49. UtilTimerStack.pop(profileKey);
  50. }
  51. }

resultCode = invokeActionOnly();:真正调用Action实际逻辑的地方

DefaultActionInvocation.java---invokeActionOnly()

  1. public String invokeActionOnly() throws Exception {
  2. return invokeAction(getAction(), proxy.getConfig());
  3. }
  4. protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
  5. String methodName = proxy.getMethod();
  6. if (LOG.isDebugEnabled()) {
  7. LOG.debug("Executing action method = " + actionConfig.getMethodName());
  8. }
  9. String timerKey = "invokeAction: " + proxy.getActionName();
  10. try {
  11. UtilTimerStack.push(timerKey);
  12. boolean methodCalled = false;
  13. Object methodResult = null;
  14. Method method = null;
  15. try {
  16. method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
  17. } catch (NoSuchMethodException e) {
  18. // hmm -- OK, try doXxx instead
  19. try {
  20. String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
  21. method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY);
  22. } catch (NoSuchMethodException e1) {
  23. // well, give the unknown handler a shot
  24. if (unknownHandlerManager.hasUnknownHandlers()) {
  25. try {
  26. methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
  27. methodCalled = true;
  28. } catch (NoSuchMethodException e2) {
  29. // throw the original one
  30. throw e;
  31. }
  32. } else {
  33. throw e;
  34. }
  35. }
  36. }
  37. if (!methodCalled) {
  38. methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);
  39. }
  40. return saveResult(actionConfig, methodResult);
  41. } catch (NoSuchMethodException e) {
  42. throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
  43. } catch (InvocationTargetException e) {
  44. // We try to return the source exception.
  45. Throwable t = e.getTargetException();
  46. if (actionEventListener != null) {
  47. String result = actionEventListener.handleException(t, getStack());
  48. if (result != null) {
  49. return result;
  50. }
  51. }
  52. if (t instanceof Exception) {
  53. throw (Exception) t;
  54. } else {
  55. throw e;
  56. }
  57. } finally {
  58. UtilTimerStack.pop(timerKey);
  59. }
  60. }

OK action执行完了,还要根据ResultConfig返回到view,也就是在invoke方法中调用executeResult方法。

  1. // now execute the result, if we're supposed to
  2. if (proxy.getExecuteResult()) {
  3. executeResult();
  4. }

DefaultActionInvocation.java---executeResult()

  1. /**
  2. * Uses getResult to get the final Result and executes it
  3. *
  4. * @throws ConfigurationException If not result can be found with the returned code
  5. */
  6. private void executeResult() throws Exception {
  7. result = createResult();
  8. String timerKey = "executeResult: " + getResultCode();
  9. try {
  10. UtilTimerStack.push(timerKey);
  11. if (result != null) {
  12. result.execute(this);
  13. } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
  14. throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
  15. + " and result " + getResultCode(), proxy.getConfig());
  16. } else {
  17. if (LOG.isDebugEnabled()) {
  18. LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
  19. }
  20. }
  21. } finally {
  22. UtilTimerStack.pop(timerKey);
  23. }
  24. }

DefaultActionInvocation.java---createResult()

  1. public Result createResult() throws Exception {
  2. if (explicitResult != null) {
  3. Result ret = explicitResult;
  4. explicitResult = null;
  5. return ret;
  6. }
  7. ActionConfig config = proxy.getConfig();
  8. Map<String, ResultConfig> results = config.getResults();
  9. ResultConfig resultConfig = null;
  10. try {
  11. resultConfig = results.get(resultCode);
  12. } catch (NullPointerException e) {
  13. if (LOG.isDebugEnabled()) {
  14. LOG.debug("Got NPE trying to read result configuration for resultCode [#0]", resultCode);
  15. }
  16. }
  17. if (resultConfig == null) {
  18. // If no result is found for the given resultCode, try to get a wildcard '*' match.
  19. resultConfig = results.get("*");
  20. }
  21. if (resultConfig != null) {
  22. try {
  23. return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
  24. } catch (Exception e) {
  25. if (LOG.isErrorEnabled()) {
  26. LOG.error("There was an exception while instantiating the result of type #0", e, resultConfig.getClassName());
  27. }
  28. throw new XWorkException(e, resultConfig);
  29. }
  30. } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
  31. return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
  32. }
  33. return null;
  34. }

具体result.execute(this);的实现可以参考一下各类中的具体代码:


 时间匆忙,只是对整个执行流程做了代码层面的演示,待后续将详细说明附上 ^.^

Struts2.3.15.1源码浅析的更多相关文章

  1. Struts2源码浅析-ConfigurationProvider

    ConfigurationProvider接口 主要完成struts配置文件 加载 注册过程 ConfigurationProvider接口定义 public interface Configurat ...

  2. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. String 源码浅析————终结篇

    写在前面 说说这几天看源码的感受吧,其实 jdk 中的源码设计是最值得进阶学习的地方.我们在对 api 较为熟悉之后,完全可以去尝试阅读一些 jdk 源码,打开 jdk 源码后,如果你英文能力稍微过得 ...

  4. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  5. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  6. (转)【深入浅出jQuery】源码浅析2--奇技淫巧

    [深入浅出jQuery]源码浅析2--奇技淫巧 http://www.cnblogs.com/coco1s/p/5303041.html

  7. HashSet其实就那么一回事儿之源码浅析

    上篇文章<HashMap其实就那么一回事儿之源码浅析>介绍了hashMap,  本次将带大家看看HashSet, HashSet其实就是基于HashMap实现, 因此,熟悉了HashMap ...

  8. Android 手势识别类 ( 三 ) GestureDetector 源码浅析

    前言:上 篇介绍了提供手势绘制的视图平台GestureOverlayView,但是在视图平台上绘制出的手势,是需要存储以及在必要的利用时加载取出手势.所 以,用户绘制出的一个完整的手势是需要一定的代码 ...

  9. Android开发之Theme、Style探索及源码浅析

    1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解 ...

随机推荐

  1. 【转】RadControls for Silverlight(学习1-GridView)

    引用:Telerik(官 网:http://www.telerik.com/)是保加利亚的一个软件公司,专注于微软.Net平台的表示层与内容管理控件.我们提供高度稳定性和丰富性能的组件产品,并可应用在 ...

  2. Number of 1 Bits(Difficulty: Easy)

    题目: Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also ...

  3. git pull错误

    1. Pull is not possible because you have unmerged files. 症状:pull的时候 $ git pull Pull is not possible ...

  4. ionic cordova 热更新(引用自www.zyyapp.com/post/116.html)

    上篇文章cordova 把html打包成安卓应用 http://www.zyyapp.com/post/115.html cordova 热更新是一个大坑,我看了一天一夜才明白.网上的教程都没说到重点 ...

  5. windows Path变量优先级

    系统>用户 且第一次配置无需重启即可使用 如遇到升级版本,需要重新配置Path,则需要重启方可生效~~

  6. VS2010的项目配置

    一直对VS的项目配置都是不怎么了解的,以前用过点,半年不用后,什么都忘记了... 下面这个是免于输入过长的引用头文件的,比如:#include “D:/C++/curl-7.37.0/libcurl/ ...

  7. java 调用 sql server存储过程

    Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这样就可以提高存储过程的性能. Ø ...

  8. 开源BTS产品中存在多处漏洞,攻击者或可劫持手机通讯基站

    前言 在过去的几周时间里,我从多个方面对GSM的安全性进行了调查和研究,例如GSM通信协议中存在的漏洞.除此之外,我还对目前世界上应用最为广泛的BTS软件进行了安全审计.在这篇文章中,我将会给大家介绍 ...

  9. 关于Python中的文件操作(转)

    总是记不住API.昨晚写的时候用到了这些,但是没记住,于是就索性整理一下吧: python中对文件.文件夹(文件操作函数)的操作需要涉及到os模块和shutil模块. 得到当前工作目录,即当前Pyth ...

  10. django文件批量上传-简写版

    模板中创建表单 <form method='post' enctype='multipart/form-data' action='/upload/'> <input type='f ...