Struts 2 调用流程

来源:互联网 发布:js随机数函数 编辑:程序博客网 时间:2024/05/17 22:58

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()方法,完成调用.

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 设置虚拟服务器输入不了数字怎么办 玩客云自动备份设备连接失败怎么办 3dmax文件打开失败怎么办 夏天吹空调嘴歪怎么办 燃气热水器温度调节拧不动怎么办 能率热水器震动声大怎么办 车底盘磕了漏油怎么办 法士特变速箱的随动阀漏气了怎么办 大灯随动afs失灵怎么办 2017款迈腾大灯随动故障怎么办 微信gps信号不好怎么办 苹果6s定位不准怎么办 电脑不读取u盘怎么办 注塑机上的料烤坨了怎么办 智能锁电机坏了怎么办 注塑机加热嘴内扣突了怎么办 tpu粘在螺杆上怎么办 注塑机锁模时会有射退动作怎么办 电动车刹车油泵不打油怎么办 cad转pdf颜色浅怎么办 松下多功能传真一体机卡纸怎么办 无刷电机坏了怎么办 6kv高压电机绝缘不合格怎么办? 400t油压机下降太慢怎么办 无法连线到服务器1~1怎么办? 数控车床车角度不亮怎么办 超市存包柜的票不见了怎么办 交货期来不及导致船期延误怎么办 跑1000米中途累怎么办 手指被机器压烂怎么办 机械手不能回归原点该怎么办 前缘送纸纸板翘怎么办 三菱AL 1R.2报警怎么办 工作好但领导不好伺候怎么办 孕妇憋尿憋的小腹疼怎么办 怀孕憋尿憋的小腹疼怎么办 半夜憋尿憋的小腹疼怎么办 新生儿大便次数较多怎么办 母猎生下三天没有奶怎么办 孩孑大便干不爱喝水怎么办 发那科1050报警怎么办