Struts2调用流程

1. 当Servlet容器接收到一个请求后,将请求交给你在wed.xml文件中配置的过滤器FilterDispatcher。
FilterDispatcher类的处理流程:
1.1 FilterDispatcher类实现了StrutsStatics, Filter这二个接口。StrutsStatics类定义了Struts2的常量。在这里不详细介绍了。主要介绍Filter接口类,它核心有三个主要方法,doFilter、init和destroy。
1.1.1 init方法的使用
 首先创建一个FilterConfig类
 通过该查询是否已经存在一个日志文件,如果不存在则创建一个日志文件。(2.0没)
 private void initLogging() {
         String factoryName = filterConfig.getInitParameter("loggerFactory");
         if (factoryName != null) {
             try {
                 Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass());
                 LoggerFactory fac = (LoggerFactory) cls.newInstance();
                 LoggerFactory.setLoggerFactory(fac);
             } catch (InstantiationException e) {
      System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default");
                 e.printStackTrace();
             } catch (IllegalAccessException e) {
      System.err.println("Unable to access logger factory: " + factoryName + ", using default");
                 e.printStackTrace();
             } catch (ClassNotFoundException e) {
      System.err.println("Unable to locate logger factory class: " + factoryName + ", using default");
                 e.printStackTrace();
             }
         }
         log = LoggerFactory.getLogger(FilterDispatcher.class);
 }
 接着调用Dispatcher createDispatcher()方法,获取wed.xml文件中的配置信息,并通过一个MAP对象进行存储。
 protected Dispatcher createDispatcher(FilterConfig filterConfig) {
         Map<String, String> params = new HashMap<String, String>();
         for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) {
             String name = (String) e.nextElement();
             String value = filterConfig.getInitParameter(name);
             params.put(name, value);
         }
         return new Dispatcher(filterConfig.getServletContext(), params);
 }
对象例子

<init-param>
   <param-name>encoding</param-name>
   <param-value>gb2312</param-value>
</init-param>
 接着把获取到的相关参数传给Dispatcher类。这个类主要实现对配置文件信息的获取,根据配置信息,让不同的action的结果返回到不同的页面。
 进入到Dispatcher类,首先调用其init()方法,获取配置信息。
○1首先实例化一个ConfigurationManager对象。
○2接着调用init_DefaultProperties()方法,这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 DefaultPropertiesProvider的register()方法可以载入org/apache/struts2/default.properties中定义的属性。
 DefaultPropertiesProvider类中的register()方法
 public void register(ContainerBuilder builder, LocatableProperties props)
             throws ConfigurationException {
         Settings defaultSettings = null;
         try {
              defaultSettings = new PropertiesSettings("org/apache/struts2/default");
         } catch (Exception e) {
   throw new ConfigurationException("Could not find or error in
   org/apache/struts2/default.properties", e);
         }
         loadSettings(props, defaultSettings);
 }

 ConfigurationManager类中的addConfigurationProvider()方法
 public void addConfigurationProvider(ConfigurationProvider provider) {
         if (!configurationProviders.contains(provider)) {
             configurationProviders.add(provider);
         }
 }

 init_DefaultProperties()方法
 private void init_DefaultProperties() {
         configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
 }

○3接着调用init_TraditionalXmlConfigurations()方法,实现载入FilterDispatcher的配置中所定义的config属性。 如果用户没有定义config属性,struts默认会载入DEFAULT_CONFIGURATION_PATHS这个值所代表的xml文件。它的值为"struts-default.xml,struts-plugin.xml,struts.xml"。也就是说框架默认会载入这三个项目xml文件。如果文件类型是XML格式,则按照xwork-x.x.dtd模板进行读取。如果,是Struts的配置文件,则按struts-2.X.dtd模板进行读取。


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

 XmlConfigurationProvider类对文件读取的模式
 public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
  LOG.info("Parsing configuration file [" + configFileName + "]");
  Map<String, Node> loadedBeans = new HashMap<String, Node>();
  for (Document doc : documents) {
   Element rootElement = doc.getDocumentElement();
   NodeList children = rootElement.getChildNodes();
   int childSize = children.getLength();
   for (int i = 0; i < childSize; i++) {
    Node childNode = children.item(i);
    if (childNode instanceof Element) {
     Element child = (Element) childNode;
     final String nodeName = child.getNodeName();
     if (nodeName.equals("bean")) {
      String type = child.getAttribute("type");
      String name = child.getAttribute("name");
      String impl = child.getAttribute("class");
      String onlyStatic = child.getAttribute("static");
      String scopeStr = child.getAttribute("scope");
      boolean optional = "true".equals(child.getAttribute("optional"));
      Scope scope = Scope.SINGLETON;
      if ("default".equals(scopeStr)) {
       scope = Scope.DEFAULT;
      } else if ("request".equals(scopeStr)) {
       scope = Scope.REQUEST;
      } else if ("session".equals(scopeStr)) {
       scope = Scope.SESSION;
      } else if ("singleton".equals(scopeStr)) {
       scope = Scope.SINGLETON;
      } else if ("thread".equals(scopeStr)) {
       scope = Scope.THREAD;
      }
      if (!TextUtils.stringSet(name)) {
       name = Container.DEFAULT_NAME;
      }
      try {
       Class cimpl = ClassLoaderUtil.loadClass(impl,
         getClass());
       Class ctype = cimpl;
       if (TextUtils.stringSet(type)) {
        ctype = ClassLoaderUtil.loadClass(type,
          getClass());
       }
       if ("true".equals(onlyStatic)) {
        // Force loading of class to detect no class def found exceptions
        cimpl.getDeclaredClasses();
        containerBuilder.injectStatics(cimpl);
       } else {
        if (containerBuilder.contains(ctype, name)) {
         Location loc = LocationUtils
           .getLocation(loadedBeans.get(ctype
             .getName()
             + name));
         throw new ConfigurationException(
           "Bean type "
             + ctype
             + " with the name "
             + name
             + " has already been loaded by "
             + loc, child);
        }
        // Force loading of class to detect no class def found exceptions
        cimpl.getDeclaredConstructors();
        if (LOG.isDebugEnabled()) {
         LOG.debug("Loaded type:" + type + " name:"
           + name + " impl:" + impl);
        }
        containerBuilder
          .factory(ctype, name,
            new LocatableFactory(name,
              ctype, cimpl, scope,
              childNode), scope);
       }
       loadedBeans.put(ctype.getName() + name, child);
      } catch (Throwable ex) {
       if (!optional) {
        throw new ConfigurationException(
          "Unable to load bean: type:" + type
            + " class:" + impl, ex,
          childNode);
       } else {
        LOG.debug("Unable to load optional class: "
          + ex);
       }
      }
     } else if (nodeName.equals("constant")) {
      String name = child.getAttribute("name");
      String value = child.getAttribute("value");
      props.setProperty(name, value, childNode);
     }
    }
   }
  }
 }

 StrutsXmlConfigurationProvider类继承于它,获取大至相同。获取那些对象后,把它们追加到ConfigurationManager对象内部的ConfigurationProvider队列中。

○4 接着调用init_LegacyStrutsProperties()方法,创建一个LegacyPropertiesConfigurationProvider类,并将它追加到ConfigurationManager对象内部的ConfigurationProvider队列中。LegacyPropertiesConfigurationProvider类载入struts.properties中的配置,这个文件中的配置可以覆盖default.properties中的。其子类是DefaultPropertiesProvider类。

 private void init_LegacyStrutsProperties() {
         configurationManager.addConfigurationProvider(
 new LegacyPropertiesConfigurationProvider());
 }

○5接着调用init_ZeroConfiguration()方法,这次处理的是FilterDispatcher的配置中所定义的actionPackages属性。该参数的值是一个以英文逗号(,)隔开的字符串,每个字符串都是一个包空间,Struts 2框架将扫描这些包空间下的Action类。实现的是零配置文件信息获取。它能够能根据web.xml中配置的actionPackages自动扫描所有Action类,并猜测其NameSpace. 再利用CodeBehind猜测Result指向的jsp,实现了struts.xml的零配置(其实也不是完全没有struts.xml,而是指struts.xml的内容不会随action的增加而膨胀)。
如果有特殊的结果指向(如redirect类型的结果),在Action处用@Result配置。
    如有package级的配置(如使用非默认的Interceptor栈),仍在struts.xml中定义package,用@ParentPackage指定。
    不过,目前ZeroConfig的Annotation较少,只有@Result、@ParentPackage,@NameSpace(java的package名不符合约定规则时使用),还有exception-Mapping之类的配置没有包含。
 private void init_ZeroConfiguration() {
  String packages = initParams.get("actionPackages");
  if (packages != null) {
      String[] names = packages.split("\\s*[,]\\s*");
      // Initialize the classloader scanner with the configured packages
      if (names.length > 0) {
          ClasspathConfigurationProvider provider =
   new ClasspathConfigurationProvider(names);
          provider.setPageLocator(
   new ServletContextPageLocator(servletContext));
          configurationManager.addConfigurationProvider(provider);
      }
  }
 }

○6接着调用init_CustomConfigurationProviders()方法,此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。负责载入用户自定义的ConfigurationProvider。

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

○7接着调用init_MethodConfigurationProvider()方法,但该方法已经被注释了。
○8接着调用init_FilterInitParameters()方法,此方法用来处理FilterDispatcher的配置中所定义的所有属性。

 private void init_FilterInitParameters() {
         configurationManager.addConfigurationProvider(new ConfigurationProvider() {
             public void destroy() {}
             public void init(Configuration configuration) throws ConfigurationException {}
             public void loadPackages() throws ConfigurationException {}
             public boolean needsReload() { return false; }
             public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
                 props.putAll(initParams);
             }
         });
 }

○9接着调用init_AliasStandardObjects()方法,并将一个BeanSelectionProvider类追加到ConfigurationManager对象内部的ConfigurationProvider队列中。BeanSelectionProvider类主要实现加载org/apache/struts2/struts-messages。

 private void init_AliasStandardObjects() {
         configurationManager.addConfigurationProvider(new BeanSelectionProvider());
 }

○10接着调用init_PreloadConfiguration()方法,构建调用上边几步添加到ConfigurationManager的getConfiguration()获取当前XWork配置对象。

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

         boolean reloadi18n = Boolean.valueOf(container.getInstance(
  String.class, StrutsConstants.STRUTS_I18N_RELOAD));
         LocalizedTextUtil.setReloadBundles(reloadi18n);
         ObjectTypeDeterminer objectTypeDeterminer = container.getInstance(ObjectTypeDeterminer.class);
         ObjectTypeDeterminerFactory.setInstance(objectTypeDeterminer);
         return container;
 }


 configurationManager.getConfiguration()方法
 public synchronized Configuration getConfiguration() {
         if (configuration == null) {
             setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName));
             try {
                 configuration.reload(getConfigurationProviders());
             } catch (ConfigurationException e) {
                 setConfiguration(null);
                 throw e;
             }
         } else {
             conditionalReload();
         }
         return configuration;
 }

○11接着调用init_CheckConfigurationReloading(container)方法,检查配置重新加载。(具体怎样不清楚)

 private void init_CheckConfigurationReloading(Container container) {
         FileManager.setReloadingConfigs("true".equals(container.getInstance(
 String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD)));
 }

○12接着调用init_CheckWebLogicWorkaround(Container container)方法,初始化weblogic相关配置。

 private void init_CheckWebLogicWorkaround(Container container) {
  // test whether param-access workaround needs to be enabled
  if (servletContext != null && servletContext.getServerInfo() != null
                      && servletContext.getServerInfo().indexOf("WebLogic") >= 0) {
  LOG.info("WebLogic server detected. Enabling Struts parameter access work-around.");
      paramsWorkaroundEnabled = true;
  } else {
      paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class,
              StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND));
  }
  synchronized(Dispatcher.class) {
      if (dispatcherListeners.size() > 0) {
          for (DispatcherListener l : dispatcherListeners) {
              l.dispatcherInitialized(this);
          }
      }
  }
 }

 接着用FilterConfig类获取wed.xml配置文件中的“packages”参数,并获取参数所有的JAVA包名的列表的值,并调用parse(packages)方法,将数它们的值一个一个的获取到一个List对象中。

 protected String[] parse(String packages) {
         if (packages == null) {
             return null;
         }
         List<String> pathPrefixes = new ArrayList<String>();
         StringTokenizer st = new StringTokenizer(packages, ", \n\t");
         while (st.hasMoreTokens()) {
             String pathPrefix = st.nextToken().replace('.', '/');
             if (!pathPrefix.endsWith("/")) {
                 pathPrefix += "/";
             }
             pathPrefixes.add(pathPrefix);
         }
         return pathPrefixes.toArray(new String[pathPrefixes.size()]);
 }

1.1.2 doFilter方法的解释,这方法实现了Action的调用。(最核心这个了)

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) req;
  HttpServletResponse response = (HttpServletResponse) res;
  ServletContext servletContext = getServletContext();
  String timerKey = "FilterDispatcher_doFilter: ";
  try {
      UtilTimerStack.push(timerKey);
      request = prepareDispatcherAndWrapRequest(request, response);
      ActionMapping mapping;
      try {
          mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
      } catch (Exception ex) {
          LOG.error("error getting ActionMapping", ex);
          dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
          return;
      }
      if (mapping == null) {
          // there is no action in this request, should we look for a static resource?
          String resourcePath = RequestUtils.getServletPath(request);
          if ("".equals(resourcePath) && null != request.getPathInfo()) {
              resourcePath = request.getPathInfo();
          }
          if (serveStatic && resourcePath.startsWith("/struts")) {
              findStaticResource(resourcePath, indAndCheckResources(resourcePath), request, response);
          } else {
              // this is a normal request, let it pass through
              chain.doFilter(request, response);
          }
          // The framework did its job here
          return;
      }
      dispatcher.serviceAction(request, response, servletContext, mapping);
  } finally {
      try {
          ActionContextCleanUp.cleanUp(req);
      } finally {
          UtilTimerStack.pop(timerKey);
      }
  }
 }

 首先实例化HttpServletRequest、HttpServletResponse、ServletContext这些对象。
 接着调用UtilTimerStack.push()方法,但是搞不明这是有什么用的。
 接着调用prepareDispatcherAndWrapRequest()方法。

 protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
         Dispatcher du = Dispatcher.getInstance();
         if (du == null) {
             Dispatcher.setInstance(dispatcher);
             dispatcher.prepare(request, response);
         } else {
             dispatcher = du;
         }
         try {
             request = dispatcher.wrapRequest(request, getServletContext());
         } catch (IOException e) {
             String message = "Could not wrap servlet request with MultipartRequestWrapper!";
             LOG.error(message, e);
             throw new ServletException(message, e);
         }
         return request;
 }

 首先调用Dispatcher.getInstance()静态方法。在该方法中调用ThreadLocal类的get()方法,获取当前线程所对应的线程局部变量。并通过它的返回,实例化一个Dispatcher对象。因此Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。

 private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
 public static Dispatcher getInstance() {
         return instance.get();
 }

 接着判断du是否为空,如果是第一次访问FilterDispatcher,那么du应该为null,这时要调用Dispatcher的prepare()方法,在该方法中主要实现对编码方式的设置。

 public void prepare(HttpServletRequest request, HttpServletResponse response) {
         String encoding = null;
         if (defaultEncoding != null) {
             encoding = defaultEncoding;
         }
         Locale locale = null;
         if (defaultLocale != null) {
             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
         }
         if (encoding != null) {
             try {
                 request.setCharacterEncoding(encoding);
             } catch (Exception e) {
               LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
             }
         }
         if (locale != null) {
             response.setLocale(locale);
         }
         if (paramsWorkaroundEnabled) {
             request.getParameter("foo");
         }
 }


 接着调用dispatcher.wrapRequest(request, getServletContext())方法,对request对象进行包装(只需进行一次)。判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传,否则返回StrutsRequestWrapper的对象处理普通请求。

 public HttpServletRequest wrapRequest(HttpServletRequest request,
 ServletContext servletContext) throws IOException {
         if (request instanceof StrutsRequestWrapper) {
             return request;
         }
         String content_type = request.getContentType();
         if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
             MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
             request = new MultiPartRequestWrapper(multi, request,
 getSaveDir(servletContext));
         } else {
             request = new StrutsRequestWrapper(request);
         }
         return request;
 }

○1MultiPartRequestWrapper类的解释(网上找的解释,觉得很全,所以就用了拿来主义了)
Struts2的MultiPartRequestWrapper来分离请求中的数据。(注意:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。)
MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。
在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。
在Struts2的JakartaMultiPartRequest类的parse方法中才会真正来调用commons-fileupload组 件的ServletFileUpload类对请求进行解析,至此,Struts2已经实现了将请求转交commons-fileupload组件对请求解 析的全过程。剩下的就是等commons-fileupload组件对请求解析完毕后,拿到分解后的数据,根据field名,依次将分解后的field名 和值放到params(HashMap类型)里,同时JakartaMultiPartRequest类重置了HttpServletRequest的好 多方法,比如熟知的getParameter、getParameterNames、getParameterValues,实际上都是从解析后得到的那 个params对象里拿数据,在这个过程,commons-fileupload组件也乖乖的把上传的文件分析好 了,JakartaMultiPartRequest也毫不客气的把分解后的文件一个一个的放到了files(HashMap类型)中,实际上此 时,commons-fileupload组件已经所有要上传的文件上传完了。至此,Struts2实现了对HttpServletRequest类的包 装,当回到MultiPartRequestWrapper类后,再取一下上述解析过程中发生的错误,然后把错误加到了自己的errors列表中了。同样我们会发现在MultiPartRequestWrapper类中,也把HttpServletRequest类的好多方法重载了,毕竟是个包装类嘛,实际上对于上传文件的请求,在Struts2后期的处理中用到的request都是MultiPartRequestWrapper类对象,比如我们调用getParameter时,直接调用的是MultiPartRequestWrapper的getParameter方法,间接调的是JakartaMultiPartRequest类对象的getParameter方法。(注:从这里,我们就可以看出,JakartaMultiPartRequest是完全设计成可以替换的类了。 )

 接着ActionMapper.getMapping(), ActionMapper类是一个接口类,其具体实现是由DefaultActionMapper类实现的。以便确定这个请求是否有对应的action调用。

 DefaultActionMapper类中的getMapping()方法
 public ActionMapping getMapping(HttpServletRequest request,
                                            ConfigurationManager configManager) {
         ActionMapping mapping = new ActionMapping();
         String uri = getUri(request);
         uri = dropExtension(uri);
         if (uri == null) {
             return null;
         }
         parseNameAndNamespace(uri, mapping, configManager);
         handleSpecialParameters(request, mapping);
         if (mapping.getName() == null) {
             return null;
         }
         if (allowDynamicMethodCalls) {
             // handle "name!method" convention.
             String name = mapping.getName();
             int exclamation = name.lastIndexOf("!");
             if (exclamation != -1) {
                 mapping.setName(name.substring(0, exclamation));
                 mapping.setMethod(name.substring(exclamation + 1));
             }
         }
         return mapping;
 }

 首先创建一个ActionMapping对象(关于ActionMapping类,它内部封装了如下5个字段)

private String name;// Action名 
private String namespace;// Action名称空间 
private String method;// 执行方法 
private Map params;// 可以通过set方法设置的参数 
private Result result;// 返回的结果
这些参数在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。

 接着调getUri(request)方法,它主要实现获取请求的URI。这个方法首先判断请求是否来自于一个jsp的include,如果是,那么请求 的"javax.servlet.include.servlet_path"属性可以获得include的页面uri,否则通过一般的方法获得请求的 uri,最后返回去掉ContextPath的请求路径,比如http://127.0.0.1:8087/test/jsp /index.jsp?param=1,返回的为/jsp/index.jsp。去掉了ContextPath和查询字符串等。

 String getUri(HttpServletRequest request) {
         String uri = (String)
                  request .getAttribute("javax.servlet.include.servlet_path");
         if (uri != null) {
             return uri;
         }
         uri = RequestUtils.getServletPath(request);
         if (uri != null && !"".equals(uri)) {
             return uri;
         }
         uri = request.getRequestURI();
         return uri.substring(request.getContextPath().length());
}

 接着调用dropExtension(uri)方法,该方法负责去掉Action的"扩展名"(默认为"action")

 String dropExtension(String name) {
         if (extensions == null) {
             return name;
         }
         Iterator it = extensions.iterator();
         while (it.hasNext()) {
             String extension = "." + (String) it.next();
             if (name.endsWith(extension)) {
                 name = name.substring(0, name.length() - extension.length());
                 return name;
             }
         }
         return null;
 }

 接着调用parseNameAndNamespace()方法, 此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。

 void parseNameAndNamespace(String uri, ActionMapping mapping,
 ConfigurationManager configManager) { 
 String namespace, name; 
 /* 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1 */ 
 /* dropExtension()后,获得uri为/namespace/name */ 
        int lastSlash = uri.lastIndexOf("/"); 
        if (lastSlash == -1) { 
           namespace = ""; 
            name = uri; 
        } else if (lastSlash == 0) { 
            namespace = "/"; 
            name = uri.substring(lastSlash + 1); 
        } else if (alwaysSelectFullNamespace) {
  // alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符全作为名称空间。 
            namespace = uri.substring(0, lastSlash);// 获得字符串 namespace 
            name = uri.substring(lastSlash + 1);// 获得字符串 name 
        } else { 
     /* 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/
   actionname.action?param=1 */ 
      /* dropExtension()后,获得uri为/namespace1/namespace2/actionname */ 
            Configuration config = configManager.getConfiguration(); 
            String prefix = uri.substring(0, lastSlash);
  // 获得 /namespace1/namespace2 
            namespace = ""; 
            /*如果配置文件中有一个包的namespace是 /namespace1/namespace2,
  那么namespace为/namespace1/namespace2,name为actionname  */
            /* 如果配置文件中有一个包的namespace是 /namespace1,
  那么namespace为/namespace1,name为/namespace2/actionname*/ 
      for (Iterator i = config.getPackageConfigs().values().iterator(); i.hasNext();) { 
                String ns = ((PackageConfig) i.next()).getNamespace(); 
                if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { 
                    if (ns.length() > namespace.length()) { 
                        namespace = ns; 
                    } 
                } 
            } 
              name = uri.substring(namespace.length() + 1); 
        } 
          if (!allowSlashesInActionNames && name != null) {
  //allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false 
            int pos = name.lastIndexOf('/'); 
            if (pos > -1 && pos < name.length() - 1) { 
                name = name.substring(pos + 1); 
            } 
        }
  // 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname 
        mapping.setNamespace(namespace); 
        mapping.setName(name); 
  }

 接着调用handleSpecialParameters()方法, 该方法将请求参数中的重复项去掉.(但该方法存在问题,具体原因见“由IE浏览器引发的Struts2的Bug(submit无法传至服务器).doc”)

 public void handleSpecialParameters(HttpServletRequest request,
             ActionMapping mapping) {
         // handle special parameter prefixes.
         Set<String> uniqueParameters = new HashSet<String>();
         Map parameterMap = request.getParameterMap();
         for (Iterator iterator = parameterMap.keySet().iterator(); iterator
                 .hasNext();) {
             String key = (String) iterator.next();
             // Strip off the image button location info, if found
             if (key.endsWith(".x") || key.endsWith(".y")) {
                 key = key.substring(0, key.length() - 2);
             }           
             // Ensure a parameter doesn't get processed twice
             if (!uniqueParameters.contains(key)) {
                 ParameterAction parameterAction = (ParameterAction) prefixTrie
                         .get(key);
                 if (parameterAction != null) {
                     parameterAction.execute(key, mapping);
                     uniqueParameters.add(key);
                     break;
                 }
             }
         }
 }

 接着判断Action的name有没有解析出来,如果没,直接返回NULL。

 if (mapping.getName() == null) {
       returnnull;
 }

 最后处理形如testAction!method格式的请求路径。

 if (allowDynamicMethodCalls) {
     // handle "name!method" convention.
     String name = mapping.getName();
     int exclamation = name.lastIndexOf("!");
     //!是Action名称和方法名的分隔符
     if (exclamation != -1) {
         mapping.setName(name.substring(0, exclamation));
         //提取左边为name
         mapping.setMethod(name.substring(exclamation + 1));
         //提取右边的method
     }
 }

ActionMapper.getMapping()流程图:

从代码中看出,getMapping()方法返回ActionMapping类型的对象,该对象包含三个参数:Action的name、namespace和要调用的方法method。
 接着,判断如果getMapping()方法返回ActionMapping对象为null,则FilterDispatcher认为用户请求不是Action, 自然另当别论,FilterDispatcher会做一件非常有意思的事:如果请求以/struts开头,会自动查找在web.xml文件中配置的 packages初始化参数,就像下面这样(注意粗斜体部分):
   <filter>
     <filter-name>struts2</filter-name>
     <filter-class>
       org.apache.struts2.dispatcher.FilterDispatcher
     </filter-class>
     <init-param>
       <param-name>packages</param-name>
       <param-value>com.lizanhong.action</param-value>
     </init-param>
   </filter>
   FilterDispatcher会将com.lizanhong.action包下的文件当作静态资源处理,(但是Struts2.0和Struts2.1对其处理不同)Struts2.0只会显示出错信息,而Struts2.1接在页面上显示文件内容,不过会忽略 扩展名为class的文件。比如在com.lizanhong.action包下有一个aaa.txt的文本文件,其内容为“中华人民共和国”,访问 http://localhost:8081/Struts2Demo/struts/aaa.txt时会有如下图的输出


 FilterDispatcher.findStaticResource()方法,就是负责查找静态资源的方法。
 接着,如getMapping()方法返回ActionMapping对象不为null,则认为正在请求某个Action,并且运行dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是ACTION处理的核心。在Dispatcher.serviceAction()方法中,先加载Struts2的配置文件,如果没有人为配置,则默认加载struts- default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如 com.opensymphony.xwork2.config.entities.XxxxConfig的类中。

 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping) throws ServletException {
         Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
         ValueStack stack = (ValueStack) request .getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
         if (stack != null) {
             extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
         }
         String timerKey = "Handling request from Dispatcher";
         //"Handling request from Dispatcher"表示处理请求调度
         try {
             UtilTimerStack.push(timerKey);
             String namespace = mapping.getNamespace();
             String name = mapping.getName();
             String method = mapping.getMethod();
             Configuration config = configurationManager.getConfiguration();
             ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, extraContext, true, false);
             proxy.setMethod(method);
             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
             if (mapping.getResult() != null) {
                 Result result = mapping.getResult();
                 result.execute(proxy.getInvocation());
             } else {
                 proxy.execute();
             }
             if (stack != null) {
                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
             }
         } catch (ConfigurationException e) {
             LOG.error("Could not find action or result", e);
             sendError(request, response, context,HttpServletResponse.SC_NOT_FOUND, e);
         } catch (Exception e) {
             throw new ServletException(e);
         } finally {
             UtilTimerStack.pop(timerKey);
         }
 }

 首先调用createContextMap()方法,这个方法首先创建了一个名称为extraContext的Map对象。它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象中查找。

 Public Map<String,Object> createContextMap(HttpServletRequest request,
 HttpServletResponse response,ActionMapping mapping, ServletContext context) { 
       Map requestMap = new RequestMap(request);// 封装了请求对象 
       Map params = null;// 封装了http参数 
     if (mapping != null) { 
         params = mapping.getParams();//从ActionMapping中获取Action的参数Map 
     } 
      Map requestParams = new HashMap(request.getParameterMap()); 
     if (params != null) { 
         params.putAll(requestParams);// 并将请求中的参数也放入Map中 
     } else { 
         params = requestParams; 
     } 
     Map session = new SessionMap(request);// 封装了session 
     Map application = new ApplicationMap(context);// 封装了ServletContext 
     /*将各个Map放入extraContext中 */ 
    Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context); 
     extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); 
     return extraContext; 
 } 

 接着判断request中是否已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入extraContext中。
 接下来是一些准备工作,如,获取了namespace,name,method等。
 接着构建一个ActionProxy对象,它负责对真实的Action进行调用,并可以在调用Action前后调用拦截器(Interceptor),其默认实现StrutsActionProxyFactory类中的createActionProxy()方法。

 public ActionProxy createActionProxy(String namespace, String actionName,
 Map extraContext,boolean executeResult, boolean cleanupContext)throws Exception {
         ActionProxy proxy = new StrutsActionProxy(namespace, actionName, extraContext, executeResult, cleanupContext);
         container.inject(proxy);
         proxy.prepare();
         return proxy;
 }

由上述的源代码可见,方法返回了一个StrutsActionProxy对象作为ActionProxy的默认实现。
 其中proxy.prepare()方法,是用DefaultActionProxy类中的prepare()默认实现。

 public void prepare() throws Exception {
  String profileKey = "create DefaultActionProxy: ";
  try {
      UtilTimerStack.push(profileKey);
      config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
      if (config == null && unknownHandler != null) {
          config = unknownHandler.handleUnknownAction(namespace, actionName);
      }
      if (config == null) {
          String message;
          if ((namespace != null) && (namespace.trim().length() > 0)) {
              message = calizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
                  namespace, actionName
              });
          } else {
              message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
                  actionName
              });
          }
          throw new ConfigurationException(message);
      }
     
      invocation = new DefaultActionInvocation(objectFactory, unknownHandler, this, extraContext, true, actionEventListener);
      resolveMethod();
  } finally {
      UtilTimerStack.pop(profileKey);
  }
 }

这里边创建了一个DefaultActionInvocation对象作为ActionInvocation对象的默认实现。

 接着调用resolveMethod()方法
 private void resolveMethod() {
         if (!TextUtils.stringSet(this.method)) {
             this.method = config.getMethodName();
             if (!TextUtils.stringSet(this.method)) {
                 this.method = "execute";
             }
         }
 }

这个方法实现了Action执行方法的设定,如果config中配置有方法名,那么就将这个方法名作为执行方法名,否则就用默认的execute。

 接着运行proxy.setMethod(method)语句,这里主要为了设置Action调用中要执行的方法.如果没有方法被指定,将会由Action的配置来提供.
 接着运行 request.setAttribute()方法,把ValueStack对象放在Request对象中,以便通过Request对象访问ValueStack中的对象.
 接着判断ActionMapping.getResult()是否为空,如果不为空,则获取相关Result对象.
 接着执行result.execute(proxy.getInvocation())方法.在proxy.getInvocation()方法的默认实现是DefaultActionProxy类的getInvocation()方法. getInvocation()方法获取一个DefaultActionInvocation对象, DefaultActionInvocation对象在定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的执行方法(如execute()方法).
 如果不为空, 执行proxy.execute()方法. ActionProxy类是通过DefaultActionProxy类来具体实现的.

 public String execute() throws Exception {
         ActionContext nestedContext = ActionContext.getContext();
         ActionContext.setContext(invocation.getInvocationContext());
         String retCode = null;
         String profileKey = "execute: ";
         try {
         UtilTimerStack.push(profileKey);
             retCode = invocation.invoke();
         } finally {
             if (cleanupContext) {
                 ActionContext.setContext(nestedContext);
             }
             UtilTimerStack.pop(profileKey);
         }
         return retCode;
 }
在其中调用了ActionInvocation类的invoke()方法,而其具体实现是由DefaultActionInvocation类的invoke()方法实现的. 该方法实现了截拦器的递归调用和执行Action的execute()方法.
 public String invoke() throws Exception {
  String profileKey = "invoke: ";
  try {
   UtilTimerStack.push(profileKey);
   if (executed) {
    throw new IllegalStateException("Action has already executed");
   }
   if (interceptors.hasNext()) {
    //从截拦器集合中取出当前的截拦器
    final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
    UtilTimerStack.profile("interceptor: " + interceptor.getName(),new UtilTimerStack.ProfilingBlock<String>() {
       public String doProfiling() throws Exception {
        resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
        return null;
       }
      });
   } else {
    resultCode = invokeActionOnly();
   }
   if (!executed) {
    if (preResultListeners != null) {
     for (Iterator iterator = preResultListeners.iterator(); iterator.hasNext();) {
      PreResultListener listener = (PreResultListener) iterator.next();
      String _profileKey = "preResultListener: ";
      try {
       UtilTimerStack.push(_profileKey);
       listener.beforeResult(this, resultCode);
      } finally {
       UtilTimerStack.pop(_profileKey);
      }
     }
    }
    if (proxy.getExecuteResult()) {
     executeResult();
    }
    executed = true;
   }
   return resultCode;
  } finally {
   UtilTimerStack.pop(profileKey);
  }
 }
}

在上述代码实现递归调用截拦器是由Interceptor 类来实现的.

 publicinterface Interceptor extends Serializable {
   void destroy();
   void init();
   String intercept(ActionInvocation invocation) throws Exception;
 }

所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用 invocation.invoke(),invoke()方法中蓝色代码会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归.
调用流程如下:

 如果截拦器全部执行完毕,则调用invokeActionOnly()方法执行Action,invokeActionOnly()方法基本没做什么工作,只调用了invokeAction()方法。

public String invokeActionOnly() throws Exception {
    return invokeAction(getAction(), proxy.getConfig());
}

 DefaultActionInvocation.invokeAction()方法实现Action的调用.
 protected String invokeAction(Object action, ActionConfig actionConfig)
   throws Exception {
  String methodName = proxy.getMethod();
  if (LOG.isDebugEnabled()) {
   LOG.debug("Executing action method = "
     + actionConfig.getMethodName());
  }
  String timerKey = "invokeAction: " + proxy.getActionName();
  try {
   UtilTimerStack.push(timerKey);
   Method method;
   try {
    method = getAction().getClass().getMethod(methodName,new Class[0]);
   } catch (NoSuchMethodException e) {
    try {
     String altMethodName = "do"+ methodName.substring(0, 1).toUpperCase()+ methodName.substring(1);
     method = getAction().getClass().getMethod(altMethodName,new Class[0]);
    } catch (NoSuchMethodException e1) {
     throw e;
    }
   }
   Object methodResult = method.invoke(action, new Object[0]);
   if (methodResult instanceof Result) {
    this.result = (Result) methodResult;
    return null;
   } else {
    return (String) methodResult;
   }
  } catch (NoSuchMethodException e) {
   throw new IllegalArgumentException("The " + methodName+ "() is not defined in action " + getAction().getClass()
     + "");
  } catch (InvocationTargetException e) {
   Throwable t = e.getTargetException();
   if (actionEventListener != null) {
    String result = actionEventListener.handleException(t,getStack());
    if (result != null) {
     return result;
    }
   }
   if (t instanceof Exception) {
    throw (Exception) t;
   } else {
    throw e;
   }
  } finally {
   UtilTimerStack.pop(timerKey);
  }
 }

由这句Object methodResult = method.invoke(action, new Object[0]);可以看出,最后通过反射实现了Action的执行方法的调用。

 接着返回invoke()方法,判断executed是否为false.如果是则调用了在PreResultListener中的定义的一些执行Result前的操作.
 接着根据配置文件中的设置执行Result.其执行方法为executeResult()方法.

 private void executeResult() throws Exception {
  result = createResult();
  String timerKey = "executeResult: " + getResultCode();
  try {
   UtilTimerStack.push(timerKey);
   if (result != null) {
    result.execute(this);
   } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
    throw new ConfigurationException(
      "No result defined for action "
        + getAction().getClass().getName()
        + " and result " + getResultCode(), proxy
        .getConfig());
   } else {
    if (LOG.isDebugEnabled()) {
     LOG.debug("No result returned for action "
       + getAction().getClass().getName() + " at "
       + proxy.getConfig().getLocation());
    }
   }
  } finally {
   UtilTimerStack.pop(timerKey);
  }
 }
 然后,返回到dispatcher.serviceAction()方法,完成调用.

你可能感兴趣的:(struts2)