`
andymu1117
  • 浏览: 36710 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

(转)Struts2调用流程

    博客分类:
  • java
阅读更多

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

分享到:
评论
10 楼 Smart_chw 2010-01-11  
<div class="quote_title">andymu1117 写道</div>
<div class="quote_div">
<p>1. 当Servlet容器接收到一个请求后,将请求交给你在wed.xml文件中配置的过滤器FilterDispatcher。 <br>FilterDispatcher类的处理流程: <br>1.1 FilterDispatcher类实现了StrutsStatics, Filter这二个接口。StrutsStatics类定义了Struts2的常量。在这里不详细介绍了。主要介绍Filter接口类,它核心有三个主要方法,doFilter、init和destroy。 <br>1.1.1 init方法的使用 <br> 首先创建一个FilterConfig类 <br> 通过该查询是否已经存在一个日志文件,如果不存在则创建一个日志文件。(2.0没) <br>private void initLogging() { <br>        String factoryName = filterConfig.getInitParameter("loggerFactory"); <br>        if (factoryName != null) { <br>            try { <br>                Class cls = ClassLoaderUtils.loadClass(factoryName, this.getClass()); <br>                LoggerFactory fac = (LoggerFactory) cls.newInstance(); <br>                LoggerFactory.setLoggerFactory(fac); <br>            } catch (InstantiationException e) { <br>   System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default"); <br>                e.printStackTrace(); <br>            } catch (IllegalAccessException e) { <br>   System.err.println("Unable to access logger factory: " + factoryName + ", using default"); <br>                e.printStackTrace(); <br>            } catch (ClassNotFoundException e) { <br>   System.err.println("Unable to locate logger factory class: " + factoryName + ", using default"); <br>                e.printStackTrace(); <br>            } <br>        } <br>        log = LoggerFactory.getLogger(FilterDispatcher.class); <br>} <br> 接着调用Dispatcher createDispatcher()方法,获取wed.xml文件中的配置信息,并通过一个MAP对象进行存储。 <br>protected Dispatcher createDispatcher(FilterConfig filterConfig) { <br>        Map&lt;String, String&gt; params = new HashMap&lt;String, String&gt;(); <br>        for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { <br>            String name = (String) e.nextElement(); <br>            String value = filterConfig.getInitParameter(name); <br>            params.put(name, value); <br>        } <br>        return new Dispatcher(filterConfig.getServletContext(), params); <br>} <br>对象例子 <br><br>&lt;init-param&gt; <br>   &lt;param-name&gt;encoding&lt;/param-name&gt; <br>   &lt;param-value&gt;gb2312&lt;/param-value&gt; <br>&lt;/init-param&gt; <br> 接着把获取到的相关参数传给Dispatcher类。这个类主要实现对配置文件信息的获取,根据配置信息,让不同的action的结果返回到不同的页面。 <br> 进入到Dispatcher类,首先调用其init()方法,获取配置信息。 <br>○1首先实例化一个ConfigurationManager对象。 <br>○2接着调用init_DefaultProperties()方法,这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 DefaultPropertiesProvider的register()方法可以载入org/apache/struts2/default.properties中定义的属性。 <br>DefaultPropertiesProvider类中的register()方法 <br>public void register(ContainerBuilder builder, LocatableProperties props) <br>            throws ConfigurationException { <br>        Settings defaultSettings = null; <br>        try { <br>            defaultSettings = new PropertiesSettings("org/apache/struts2/default"); <br>        } catch (Exception e) { <br>throw new ConfigurationException("Could not find or error in <br>org/apache/struts2/default.properties", e); <br>        } <br>        loadSettings(props, defaultSettings); <br>} <br><br>ConfigurationManager类中的addConfigurationProvider()方法 <br>public void addConfigurationProvider(ConfigurationProvider provider) { <br>        if (!configurationProviders.contains(provider)) { <br>            configurationProviders.add(provider); <br>        } <br>} <br><br>init_DefaultProperties()方法 <br>private void init_DefaultProperties() { <br>        configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); <br>} <br><br>○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模板进行读取。 <br><br><br>private void init_TraditionalXmlConfigurations() { <br>        String configPaths = initParams.get("config"); <br>        if (configPaths == null) { <br>            configPaths = DEFAULT_CONFIGURATION_PATHS; <br>        } <br>        String[] files = configPaths.split("\\s*[,]\\s*"); <br>        for (String file : files) { <br>            if (file.endsWith(".xml")) { <br>                if ("xwork.xml".equals(file)) { <br>                    configurationManager.addConfigurationProvider( <br>                                new XmlConfigurationProvider(file, false)); <br>                } else { <br>                    configurationManager.addConfigurationProvider( <br>new StrutsXmlConfigurationProvider(file, false, servletContext)); <br>                } <br>            } else { <br>           throw new IllegalArgumentException("Invalid configuration file name"); <br>            } <br>        } <br>} <br><br>XmlConfigurationProvider类对文件读取的模式 <br>public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { <br>        LOG.info("Parsing configuration file [" + configFileName + "]"); <br>        Map&lt;String, Node&gt; loadedBeans = new HashMap&lt;String, Node&gt;(); <br>        for (Document doc : documents) { <br>            Element rootElement = doc.getDocumentElement(); <br>            NodeList children = rootElement.getChildNodes(); <br>            int childSize = children.getLength(); <br>            for (int i = 0; i &lt; childSize; i++) { <br>                Node childNode = children.item(i); <br>                if (childNode instanceof Element) { <br>                    Element child = (Element) childNode; <br>                    final String nodeName = child.getNodeName(); <br>                    if (nodeName.equals("bean")) { <br>                        String type = child.getAttribute("type"); <br>                        String name = child.getAttribute("name"); <br>                        String impl = child.getAttribute("class"); <br>                        String onlyStatic = child.getAttribute("static"); <br>                        String scopeStr = child.getAttribute("scope"); <br>                        boolean optional = <br>"true".equals(child.getAttribute("optional")); <br>                        Scope scope = Scope.SINGLETON; <br>                        if ("default".equals(scopeStr)) { <br>                            scope = Scope.DEFAULT; <br>                        } else if ("request".equals(scopeStr)) { <br>                            scope = Scope.REQUEST; <br>                        } else if ("session".equals(scopeStr)) { <br>                            scope = Scope.SESSION; <br>                        } else if ("singleton".equals(scopeStr)) { <br>                            scope = Scope.SINGLETON; <br>                        } else if ("thread".equals(scopeStr)) { <br>                            scope = Scope.THREAD; <br>                        } <br>                        if (!TextUtils.stringSet(name)) { <br>                            name = Container.DEFAULT_NAME; <br>                        } <br>                        try { <br>                            Class cimpl = ClassLoaderUtil.loadClass( <br>impl, getClass()); <br>                            Class ctype = cimpl; <br>                            if (TextUtils.stringSet(type)) { <br>                                ctype = ClassLoaderUtil.loadClass( <br>type, getClass()); <br>                            } <br>                            if ("true".equals(onlyStatic)) { <br>                  // Force loading of class to detect no class def found exceptions <br>                                cimpl.getDeclaredClasses(); <br>                                containerBuilder.injectStatics(cimpl); <br>                            } else { <br>                                if (containerBuilder.contains(ctype, name)) { <br>                                    Location loc = LocationUtils <br>.getLocation(loadedBeans.get(ctype.getName() + name)); <br>                                    throw new ConfigurationException("Bean type " + ctype + " with the name " + <br>                                            name + " has already been loaded by " + loc, child); <br>                                } <br>                  // Force loading of class to detect no class def found exceptions <br>                                cimpl.getDeclaredConstructors(); <br>                                if (LOG.isDebugEnabled()) { <br>            LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl); <br>                                } <br>                                containerBuilder.factory(ctype, name, <br>new LocatableFactory(name, ctype, cimpl, scope, childNode), scope); <br>                            } <br>                            loadedBeans.put(ctype.getName() + name, child); <br>                        } catch (Throwable ex) { <br>                            if (!optional) { <br>                                throw new ConfigurationException( <br>"Unable to load bean: type:" + type + " class:" + impl, ex, childNode); <br>                            } else { <br>                                LOG.debug("Unable to load optional class: " + ex); <br>                            } <br>                        } <br>                    } else if (nodeName.equals("constant")) { <br>                        String name = child.getAttribute("name"); <br>                        String value = child.getAttribute("value"); <br>                        props.setProperty(name, value, childNode); <br>                    } <br>                } <br>            } <br>        } <br>    } <br><br>StrutsXmlConfigurationProvider类继承于它,获取大至相同。获取那些对象后,把它们追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 <br><br>○4 接着调用init_LegacyStrutsProperties()方法,创建一个LegacyPropertiesConfigurationProvider类,并将它追加到ConfigurationManager对象内部的ConfigurationProvider队列中。LegacyPropertiesConfigurationProvider类载入struts.properties中的配置,这个文件中的配置可以覆盖default.properties中的。其子类是DefaultPropertiesProvider类。 <br><br>private void init_LegacyStrutsProperties() { <br>        configurationManager.addConfigurationProvider( <br>new LegacyPropertiesConfigurationProvider()); <br>} <br><br>○5接着调用init_ZeroConfiguration()方法,这次处理的是FilterDispatcher的配置中所定义的actionPackages属性。该参数的值是一个以英文逗号(,)隔开的字符串,每个字符串都是一个包空间,Struts 2框架将扫描这些包空间下的Action类。实现的是零配置文件信息获取。它能够能根据web.xml中配置的actionPackages自动扫描所有Action类,并猜测其NameSpace. 再利用CodeBehind猜测Result指向的jsp,实现了struts.xml的零配置(其实也不是完全没有struts.xml,而是指struts.xml的内容不会随action的增加而膨胀)。 <br>如果有特殊的结果指向(如redirect类型的结果),在Action处用@Result配置。 <br>    如有package级的配置(如使用非默认的Interceptor栈),仍在struts.xml中定义package,用@ParentPackage指定。 <br>    不过,目前ZeroConfig的Annotation较少,只有@Result、@ParentPackage,@NameSpace(java的package名不符合约定规则时使用),还有exception-Mapping之类的配置没有包含。 <br>private void init_ZeroConfiguration() { <br>        String packages = initParams.get("actionPackages"); <br>        if (packages != null) { <br>            String[] names = packages.split("\\s*[,]\\s*"); <br>            // Initialize the classloader scanner with the configured packages <br>            if (names.length &gt; 0) { <br>                ClasspathConfigurationProvider provider = <br>new ClasspathConfigurationProvider(names); <br>                provider.setPageLocator( <br>new ServletContextPageLocator(servletContext)); <br>                configurationManager.addConfigurationProvider(provider); <br>            } <br>        } <br>    } <br><br>○6接着调用init_CustomConfigurationProviders()方法,此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。负责载入用户自定义的ConfigurationProvider。 <br><br>private void init_CustomConfigurationProviders() { <br>        String configProvs = initParams.get("configProviders"); <br>        if (configProvs != null) { <br>            String[] classes = configProvs.split("\\s*[,]\\s*"); <br>            for (String cname : classes) { <br>                try { <br>                    Class cls = ClassLoaderUtils.loadClass(cname, <br>this.getClass()); <br>                    ConfigurationProvider prov = <br>(ConfigurationProvider)cls.newInstance(); <br>                    configurationManager.addConfigurationProvider(prov); <br>                } catch (InstantiationException e) { <br>                    throw new ConfigurationException( <br>"Unable to instantiate provider: "+cname, e); <br>                } catch (IllegalAccessException e) { <br>                    throw new ConfigurationException( <br>"Unable to access provider: "+cname, e); <br>                } catch (ClassNotFoundException e) { <br>                    throw new ConfigurationException( <br>"Unable to locate provider class: "+cname, e); <br>                } <br>            } <br>        } <br>    } <br><br>○7接着调用init_MethodConfigurationProvider()方法,但该方法已经被注释了。 <br>○8接着调用init_FilterInitParameters()方法,此方法用来处理FilterDispatcher的配置中所定义的所有属性。 <br><br>private void init_FilterInitParameters() { <br>        configurationManager.addConfigurationProvider( <br>new ConfigurationProvider() { <br>            public void destroy() {} <br>            public void init(Configuration configuration) <br>throws ConfigurationException {} <br>            public void loadPackages() throws ConfigurationException {} <br>            public boolean needsReload() { return false; } <br>            public void register(ContainerBuilder builder, <br>LocatableProperties props) throws ConfigurationException { <br>                props.putAll(initParams); <br>            } <br>        }); <br>} <br><br>○9接着调用init_AliasStandardObjects()方法,并将一个BeanSelectionProvider类追加到ConfigurationManager对象内部的ConfigurationProvider队列中。BeanSelectionProvider类主要实现加载org/apache/struts2/struts-messages。 <br><br>private void init_AliasStandardObjects() { <br>        configurationManager.addConfigurationProvider( <br>new BeanSelectionProvider()); <br>} <br><br>○10接着调用init_PreloadConfiguration()方法,构建调用上边几步添加到ConfigurationManager的getConfiguration()获取当前XWork配置对象。 <br><br>private Container init_PreloadConfiguration() { <br>        Configuration config = configurationManager.getConfiguration(); <br>        Container container = config.getContainer(); <br><br>        boolean reloadi18n = Boolean.valueOf(container.getInstance( <br>String.class, StrutsConstants.STRUTS_I18N_RELOAD)); <br>        LocalizedTextUtil.setReloadBundles(reloadi18n); <br>        ObjectTypeDeterminer objectTypeDeterminer = <br>container.getInstance(ObjectTypeDeterminer.class); <br>        ObjectTypeDeterminerFactory.setInstance(objectTypeDeterminer); <br>        return container; <br>} <br><br><br>configurationManager.getConfiguration()方法 <br>public synchronized Configuration getConfiguration() { <br>        if (configuration == null) { <br>            setConfiguration(new DefaultConfiguration(defaultFrameworkBeanName)); <br>            try { <br>                configuration.reload(getConfigurationProviders()); <br>            } catch (ConfigurationException e) { <br>                setConfiguration(null); <br>                throw e; <br>            } <br>        } else { <br>            conditionalReload(); <br>        } <br>        return configuration; <br>    } <br><br>○11接着调用init_CheckConfigurationReloading(container)方法,检查配置重新加载。(具体怎样不清楚) <br><br>private void init_CheckConfigurationReloading(Container container) { <br>        FileManager.setReloadingConfigs("true".equals(container.getInstance( <br>String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD))); <br>} <br><br>○12接着调用init_CheckWebLogicWorkaround(Container container)方法,初始化weblogic相关配置。 <br><br>private void init_CheckWebLogicWorkaround(Container container) { <br>        // test whether param-access workaround needs to be enabled <br>        if (servletContext != null &amp;&amp; servletContext.getServerInfo() != null <br>                            &amp;&amp; servletContext.getServerInfo().indexOf("WebLogic") &gt;= 0) { <br>        LOG.info("WebLogic server detected. Enabling Struts parameter access work-around."); <br>            paramsWorkaroundEnabled = true; <br>        } else { <br>            paramsWorkaroundEnabled = "true".equals(container.getInstance(String.class, <br>                    StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)); <br>        } <br>        synchronized(Dispatcher.class) { <br>            if (dispatcherListeners.size() &gt; 0) { <br>                for (DispatcherListener l : dispatcherListeners) { <br>                    l.dispatcherInitialized(this); <br>                } <br>            } <br>        } <br>    } <br><br> 接着用FilterConfig类获取wed.xml配置文件中的“packages”参数,并获取参数所有的JAVA包名的列表的值,并调用parse(packages)方法,将数它们的值一个一个的获取到一个List对象中。 <br><br>protected String[] parse(String packages) { <br>        if (packages == null) { <br>            return null; <br>        } <br>        List&lt;String&gt; pathPrefixes = new ArrayList&lt;String&gt;(); <br>        StringTokenizer st = new StringTokenizer(packages, ", \n\t"); <br>        while (st.hasMoreTokens()) { <br>            String pathPrefix = st.nextToken().replace('.', '/'); <br>            if (!pathPrefix.endsWith("/")) { <br>                pathPrefix += "/"; <br>            } <br>            pathPrefixes.add(pathPrefix); <br>        } <br>        return pathPrefixes.toArray(new String[pathPrefixes.size()]); <br>} <br><br>1.1.2 doFilter方法的解释,这方法实现了Action的调用。(最核心这个了) <br><br>public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) <br>throws IOException, ServletException { <br>        HttpServletRequest request = (HttpServletRequest) req; <br>        HttpServletResponse response = (HttpServletResponse) res; <br>        ServletContext servletContext = getServletContext(); <br>        String timerKey = "FilterDispatcher_doFilter: "; <br>        try { <br>            UtilTimerStack.push(timerKey); <br>            request = prepareDispatcherAndWrapRequest(request, response); <br>            ActionMapping mapping; <br>            try { <br>                mapping = actionMapper.getMapping(request, <br>dispatcher.getConfigurationManager()); <br>            } catch (Exception ex) { <br>                LOG.error("error getting ActionMapping", ex); <br>                dispatcher.sendError(request, response, <br>servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); <br>                return; <br>            } <br>            if (mapping == null) { <br>                // there is no action in this request, should we look for a static resource? <br>                String resourcePath = RequestUtils.getServletPath(request); <br>                if ("".equals(resourcePath) &amp;&amp; null != request.getPathInfo()) { <br>                    resourcePath = request.getPathInfo(); <br>                } <br>                if (serveStatic &amp;&amp; resourcePath.startsWith("/struts")) { <br>                    findStaticResource(resourcePath, indAndCheckResources(resourcePath), <br>request, response); <br>                } else { <br>                    // this is a normal request, let it pass through <br>                    chain.doFilter(request, response); <br>                } <br>                // The framework did its job here <br>                return; <br>            } <br>            dispatcher.serviceAction(request, response, servletContext, mapping); <br>        } finally { <br>            try { <br>                ActionContextCleanUp.cleanUp(req); <br>            } finally { <br>                UtilTimerStack.pop(timerKey); <br>            } <br>        } <br>    } <br><br> 首先实例化HttpServletRequest、HttpServletResponse、ServletContext这些对象。 <br> 接着调用UtilTimerStack.push()方法,但是搞不明这是有什么用的。 <br> 接着调用prepareDispatcherAndWrapRequest()方法。 <br><br>protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, <br>HttpServletResponse response) throws ServletException { <br>        Dispatcher du = Dispatcher.getInstance(); <br>        if (du == null) { <br>            Dispatcher.setInstance(dispatcher); <br>            dispatcher.prepare(request, response); <br>        } else { <br>            dispatcher = du; <br>        } <br>        try { <br>            request = dispatcher.wrapRequest(request, getServletContext()); <br>        } catch (IOException e) { <br>            String message = <br>"Could not wrap servlet request with MultipartRequestWrapper!"; <br>            LOG.error(message, e); <br>            throw new ServletException(message, e); <br>        } <br>        return request; <br>} <br><br> 首先调用Dispatcher.getInstance()静态方法。在该方法中调用ThreadLocal类的get()方法,获取当前线程所对应的线程局部变量。并通过它的返回,实例化一个Dispatcher对象。因此Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。 <br><br>private static ThreadLocal&lt;Dispatcher&gt; instance = new ThreadLocal&lt;Dispatcher&gt;(); <br>public static Dispatcher getInstance() { <br>        return instance.get(); <br>} <br><br> 接着判断du是否为空,如果是第一次访问FilterDispatcher,那么du应该为null,这时要调用Dispatcher的prepare()方法,在该方法中主要实现对编码方式的设置。 <br><br>public void prepare(HttpServletRequest request, HttpServletResponse response) { <br>        String encoding = null; <br>        if (defaultEncoding != null) { <br>            encoding = defaultEncoding; <br>        } <br>        Locale locale = null; <br>        if (defaultLocale != null) { <br>            locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); <br>        } <br>        if (encoding != null) { <br>            try { <br>                request.setCharacterEncoding(encoding); <br>            } catch (Exception e) { <br>              LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e); <br>            } <br>        } <br>        if (locale != null) { <br>            response.setLocale(locale); <br>        } <br>        if (paramsWorkaroundEnabled) { <br>            request.getParameter("foo"); <br>        } <br>} <br><br><br> 接着调用dispatcher.wrapRequest(request, getServletContext())方法,对request对象进行包装(只需进行一次)。判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传,否则返回StrutsRequestWrapper的对象处理普通请求。 <br><br>public HttpServletRequest wrapRequest(HttpServletRequest request, <br>ServletContext servletContext) throws IOException { <br>        if (request instanceof StrutsRequestWrapper) { <br>            return request; <br>        } <br>        String content_type = request.getContentType(); <br>        if (content_type != null &amp;&amp; content_type.indexOf("multipart/form-data") != -1) { <br>            MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class); <br>            request = new MultiPartRequestWrapper(multi, request, <br>getSaveDir(servletContext)); <br>        } else { <br>            request = new StrutsRequestWrapper(request); <br>        } <br>        return request; <br>} <br><br>○1MultiPartRequestWrapper类的解释(网上找的解释,觉得很全,所以就用了拿来主义了) <br>Struts2的MultiPartRequestWrapper来分离请求中的数据。(注意:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。) <br>MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。 <br>在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。 <br>在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是完全设计成可以替换的类了。 ) <br><br> 接着ActionMapper.getMapping(), ActionMapper类是一个接口类,其具体实现是由DefaultActionMapper类实现的。以便确定这个请求是否有对应的action调用。 <br><br>DefaultActionMapper类中的getMapping()方法 <br>public ActionMapping getMapping(HttpServletRequest request, <br>                                           ConfigurationManager configManager) { <br>        ActionMapping mapping = new ActionMapping(); <br>        String uri = getUri(request); <br>        uri = dropExtension(uri); <br>        if (uri == null) { <br>            return null; <br>        } <br>        parseNameAndNamespace(uri, mapping, configManager); <br>        handleSpecialParameters(request, mapping); <br>        if (mapping.getName() == null) { <br>            return null; <br>        } <br>        if (allowDynamicMethodCalls) { <br>            // handle "name!method" convention. <br>            String name = mapping.getName(); <br>            int exclamation = name.lastIndexOf("!"); <br>            if (exclamation != -1) { <br>                mapping.setName(name.substring(0, exclamation)); <br>                mapping.setMethod(name.substring(exclamation + 1)); <br>            } <br>        } <br>        return mapping; <br>} <br><br> 首先创建一个ActionMapping对象(关于ActionMapping类,它内部封装了如下5个字段) <br><br>private String name;// Action名  <br>private String namespace;// Action名称空间  <br>private String method;// 执行方法  <br>private Map params;// 可以通过set方法设置的参数  <br>private Result result;// 返回的结果 <br>这些参数在配置文件中都是可设置的,确定了ActionMapping类的各个字段的值,就可以对请求的Action进行调用了。 <br><br> 接着调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和查询字符串等。 <br><br>String getUri(HttpServletRequest request) { <br>        String uri = (String) <br>                 request .getAttribute("javax.servlet.include.servlet_path"); <br>        if (uri != null) { <br>            return uri; <br>        } <br>        uri = RequestUtils.getServletPath(request); <br>        if (uri != null &amp;&amp; !"".equals(uri)) { <br>            return uri; <br>        } <br>        uri = request.getRequestURI(); <br>        return uri.substring(request.getContextPath().length()); <br>} <br><br> 接着调用dropExtension(uri)方法,该方法负责去掉Action的"扩展名"(默认为"action") <br><br>String dropExtension(String name) { <br>        if (extensions == null) { <br>            return name; <br>        } <br>        Iterator it = extensions.iterator(); <br>        while (it.hasNext()) { <br>            String extension = "." + (String) it.next(); <br>            if (name.endsWith(extension)) { <br>                name = name.substring(0, name.length() - extension.length()); <br>                return name; <br>            } <br>        } <br>        return null; <br>} <br><br> 接着调用parseNameAndNamespace()方法, 此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。 <br><br>void parseNameAndNamespace(String uri, ActionMapping mapping, <br>ConfigurationManager configManager) {  <br>String namespace, name;  <br>/* 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1 */  <br>/* dropExtension()后,获得uri为/namespace/name */  <br>       int lastSlash = uri.lastIndexOf("/");  <br>       if (lastSlash == -1) {  <br>          namespace = "";  <br>           name = uri;  <br>       } else if (lastSlash == 0) {  <br>           namespace = "/";  <br>           name = uri.substring(lastSlash + 1);  <br>       } else if (alwaysSelectFullNamespace) { <br>// alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符全作为名称空间。  <br>           namespace = uri.substring(0, lastSlash);// 获得字符串 namespace  <br>           name = uri.substring(lastSlash + 1);// 获得字符串 name  <br>       } else {  <br>   /* 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/ <br>actionname.action?param=1 */  <br>    /* dropExtension()后,获得uri为/namespace1/namespace2/actionname */  <br>           Configuration config = configManager.getConfiguration();  <br>           String prefix = uri.substring(0, lastSlash); <br>// 获得 /namespace1/namespace2  <br>           namespace = "";  <br>           /*如果配置文件中有一个包的namespace是 /namespace1/namespace2, <br>那么namespace为/namespace1/namespace2,name为actionname  */ <br>           /* 如果配置文件中有一个包的namespace是 /namespace1, <br>那么namespace为/namespace1,name为/namespace2/actionname*/  <br>     for (Iterator i = config.getPackageConfigs().values().iterator(); <br>i.hasNext();) {  <br>               String ns = ((PackageConfig) i.next()).getNamespace();  <br>               if (ns != null &amp;&amp; prefix.startsWith(ns) &amp;&amp; <br>(prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {  <br>                   if (ns.length() &gt; namespace.length()) {  <br>                       namespace = ns;  <br>                   }  <br>               }  <br>           }  <br>             name = uri.substring(namespace.length() + 1);  <br>       }  <br>         if (!allowSlashesInActionNames &amp;&amp; name != null) { <br>//allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false  <br>           int pos = name.lastIndexOf('/');  <br>           if (pos &gt; -1 &amp;&amp; pos &lt; name.length() - 1) {  <br>               name = name.substring(pos + 1);  <br>           }  <br>       } <br>// 以 name = /namespace2/actionname 为例,经过这个if块后,name = actionname  <br>       mapping.setNamespace(namespace);  <br>       mapping.setName(name);  <br>   } <br><br> 接着调用handleSpecialParameters()方法, 该方法将请求参数中的重复项去掉.(但该方法存在问题,具体原因见“由IE浏览器引发的Struts2的Bug(submit无法传至服务器).doc”) <br><br>public void handleSpecialParameters(HttpServletRequest request, <br>            ActionMapping mapping) { <br>        // handle special parameter prefixes. <br>        Set&lt;String&gt; uniqueParameters = new HashSet&lt;String&gt;(); <br>        Map parameterMap = request.getParameterMap(); <br>        for (Iterator iterator = parameterMap.keySet().iterator(); iterator <br>                .hasNext();) { <br>            String key = (String) iterator.next(); <br>            // Strip off the image button location info, if found <br>            if (key.endsWith(".x") || key.endsWith(".y")) { <br>                key = key.substring(0, key.length() - 2); <br>            }            <br>            // Ensure a parameter doesn't get processed twice <br>            if (!uniqueParameters.contains(key)) { <br>                ParameterAction parameterAction = (ParameterAction) prefixTrie <br>                        .get(key); <br>                if (parameterAction != null) { <br>                    parameterAction.execute(key, mapping); <br>                    uniqueParameters.add(key); <br>                    break; <br>                } <br>            } <br>        } <br>} <br><br> 接着判断Action的name有没有解析出来,如果没,直接返回NULL。 <br><br>if (mapping.getName() == null) { <br>      returnnull; <br>} <br><br> 最后处理形如testAction!method格式的请求路径。 <br><br>if (allowDynamicMethodCalls) { <br>            // handle "name!method" convention. <br>            String name = mapping.getName(); <br>            int exclamation = name.lastIndexOf("!"); <br>//!是Action名称和方法名的分隔符 <br>            if (exclamation != -1) { <br>                mapping.setName(name.substring(0, exclamation)); <br>                //提取左边为name <br>                mapping.setMethod(name.substring(exclamation + 1)); <br>                //提取右边的method <br>            } <br>} <br><br>ActionMapper.getMapping()流程图: <br><br>从代码中看出,getMapping()方法返回ActionMapping类型的对象,该对象包含三个参数:Action的name、namespace和要调用的方法method。 <br> 接着,判断如果getMapping()方法返回ActionMapping对象为null,则FilterDispatcher认为用户请求不是Action, 自然另当别论,FilterDispatcher会做一件非常有意思的事:如果请求以/struts开头,会自动查找在web.xml文件中配置的 packages初始化参数,就像下面这样(注意粗斜体部分): <br>  &lt;filter&gt; <br>    &lt;filter-name&gt;struts2&lt;/filter-name&gt; <br>    &lt;filter-class&gt; <br>      org.apache.struts2.dispatcher.FilterDispatcher <br>    &lt;/filter-class&gt; <br>    &lt;init-param&gt; <br>      &lt;param-name&gt;packages&lt;/param-name&gt; <br>      &lt;param-value&gt;com.lizanhong.action&lt;/param-value&gt; <br>    &lt;/init-param&gt; <br>  &lt;/filter&gt; <br>   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时会有如下图的输出 <br><br><br> FilterDispatcher.findStaticResource()方法,就是负责查找静态资源的方法。 <br> 接着,如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的类中。 <br><br>public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping) throws ServletException { <br>        Map&lt;String, Object&gt; extraContext = <br>createContextMap(request, response, mapping, context); <br>        ValueStack stack = (ValueStack) request <br>.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); <br>        if (stack != null) { <br>            extraContext.put(ActionContext.VALUE_STACK, <br>ValueStackFactory.getFactory().createValueStack(stack)); <br>        } <br>        String timerKey = "Handling request from Dispatcher"; <br>        //"Handling request from Dispatcher"表示处理请求调度 <br>        try { <br>            UtilTimerStack.push(timerKey); <br>            String namespace = mapping.getNamespace(); <br>            String name = mapping.getName(); <br>            String method = mapping.getMethod(); <br>            Configuration config = configurationManager.getConfiguration(); <br>            ActionProxy proxy = config.getContainer() <br>.getInstance(ActionProxyFactory.class).createActionProxy( <br>                    namespace, name, extraContext, true, false); <br>            proxy.setMethod(method); <br>            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); <br>            if (mapping.getResult() != null) { <br>                Result result = mapping.getResult(); <br>                result.execute(proxy.getInvocation()); <br>            } else { <br>                proxy.execute(); <br>            } <br>            if (stack != null) { <br>                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); <br>            } <br>        } catch (ConfigurationException e) { <br>            LOG.error("Could not find action or result", e); <br>            sendError(request, response, context, <br>HttpServletResponse.SC_NOT_FOUND, e); <br>        } catch (Exception e) { <br>            throw new ServletException(e); <br>        } finally { <br>            UtilTimerStack.pop(timerKey); <br>        } <br>} <br><br> 首先调用createContextMap()方法,这个方法首先创建了一个名称为extraContext的Map对象。它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象中查找。 <br><br>Public Map&lt;String,Object&gt; createContextMap(HttpServletRequest request, <br>HttpServletResponse response,ActionMapping mapping, ServletContext context) {  <br>      Map requestMap = new RequestMap(request);// 封装了请求对象  <br>      Map params = null;// 封装了http参数  <br>    if (mapping != null) {  <br>        params = mapping.getParams();//从ActionMapping中获取Action的参数Map  <br>    }  <br>     Map requestParams = new HashMap(request.getParameterMap());  <br>    if (params != null) {  <br>        params.putAll(requestParams);// 并将请求中的参数也放入Map中  <br>    } else {  <br>        params = requestParams;  <br>    }  <br>    Map session = new SessionMap(request);// 封装了session  <br>    Map application = new ApplicationMap(context);// 封装了ServletContext  <br>    /*将各个Map放入extraContext中 */  <br>   Map&lt;String,Object&gt; extraContext = createContextMap(requestMap, params, session, application, request, response, context);  <br>    extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);  <br>    return extraContext;  <br>}  <br><br> 接着判断request中是否已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入extraContext中。 <br> 接下来是一些准备工作,如,获取了namespace,name,method等。 <br> 接着构建一个ActionProxy对象,它负责对真实的Action进行调用,并可以在调用Action前后调用拦截器(Interceptor),其默认实现StrutsActionProxyFactory类中的createActionProxy()方法。 <br><br>public ActionProxy createActionProxy(String namespace, String actionName, <br>Map extraContext,boolean executeResult, boolean cleanupContext)throws Exception { <br>        ActionProxy proxy = new StrutsActionProxy(namespace, actionName, <br>extraContext, executeResult, cleanupContext); <br>        container.inject(proxy); <br>        proxy.prepare(); <br>        return proxy; <br>    } <br><br>由上述的源代码可见,方法返回了一个StrutsActionProxy对象作为ActionProxy的默认实现。 <br> 其中proxy.prepare()方法,是用DefaultActionProxy类中的prepare()默认实现。 <br><br>public void prepare() throws Exception { <br>        String profileKey = "create DefaultActionProxy: "; <br>        try { <br>            UtilTimerStack.push(profileKey); <br>            config = configuration.getRuntimeConfiguration() <br>.getActionConfig(namespace, actionName); <br>            if (config == null &amp;&amp; unknownHandler != null) { <br>                config = unknownHandler.handleUnknownAction(namespace, actionName); <br>            } <br>            if (config == null) { <br>                String message; <br>                if ((namespace != null) &amp;&amp; (namespace.trim().length() &gt; 0)) { <br>                    message = <br>  calizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ <br>                        namespace, actionName <br>                    }); <br>                } else { <br>                    message = <br>LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{ <br>                        actionName <br>                    }); <br>                } <br>                throw new ConfigurationException(message); <br>            } <br>            <br>            invocation = new DefaultActionInvocation(objectFactory, unknownHandler, this, <br>extraContext, true, actionEventListener); <br>            resolveMethod(); <br>        } finally { <br>            UtilTimerStack.pop(profileKey); <br>        } <br>    } <br><br>这里边创建了一个DefaultActionInvocation对象作为ActionInvocation对象的默认实现。 <br><br> 接着调用resolveMethod()方法 <br>private void resolveMethod() { <br>        if (!TextUtils.stringSet(this.method)) { <br>            this.method = config.getMethodName(); <br>            if (!TextUtils.stringSet(this.method)) { <br>                this.method = "execute"; <br>            } <br>        } <br>} <br><br>这个方法实现了Action执行方法的设定,如果config中配置有方法名,那么就将这个方法名作为执行方法名,否则就用默认的execute。 <br><br> 接着运行proxy.setMethod(method)语句,这里主要为了设置Action调用中要执行的方法.如果没有方法被指定,将会由Action的配置来提供. <br> 接着运行 request.setAttribute()方法,把ValueStack对象放在Request对象中,以便通过Request对象访问ValueStack中的对象. <br> 接着判断ActionMapping.getResult()是否为空,如果不为空,则获取相关Result对象. <br> 接着执行result.execute(proxy.getInvocation())方法.在proxy.getInvocation()方法的默认实现是DefaultActionProxy类的getInvocation()方法. getInvocation()方法获取一个DefaultActionInvocation对象, DefaultActionInvocation对象在定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的执行方法(如execute()方法). <br> 如果不为空, 执行proxy.execute()方法. ActionProxy类是通过DefaultActionProxy类来具体实现的. <br><br>public String execute() throws Exception { <br>        ActionContext nestedContext = ActionContext.getContext(); <br>        ActionContext.setContext(invocation.getInvocationContext()); <br>        String retCode = null; <br>        String profileKey = "execute: "; <br>        try { <br>        UtilTimerStack.push(profileKey); <br>            retCode = invocation.invoke(); <br>        } finally { <br>            if (cleanupContext) { <br>                ActionContext.setContext(nestedContext); <br>            } <br>            UtilTimerStack.pop(profileKey); <br>        } <br>        return retCode; <br>} <br>在其中调用了ActionInvocation类的invoke()方法,而其具体实现是由DefaultActionInvocation类的invoke()方法实现的. 该方法实现了截拦器的递归调用和执行Action的execute()方法. <br><br>public String invoke() throws Exception { <br>    String profileKey = "invoke: "; <br>    try { <br>    UtilTimerStack.push(profileKey); <br>    if (executed) { <br>    throw new IllegalStateException("Action has already executed"); <br>    } <br>    if (interceptors.hasNext()) { <br>                //从截拦器集合中取出当前的截拦器 <br>    final InterceptorMapping interceptor = <br>(InterceptorMapping) interceptors.next(); <br>    UtilTimerStack.profile("interceptor: "+interceptor.getName(), <br>    new UtilTimerStack.ProfilingBlock&lt;String&gt;() { <br>public String doProfiling() throws Exception { <br>    resultCode = interceptor.getInterceptor() <br>.intercept(DefaultActionInvocation.this); <br>    return null; <br>} <br>    }); <br>    } else { <br>    resultCode = invokeActionOnly(); <br>    } <br>    if (!executed) { <br>    if (preResultListeners != null) { <br>    for (Iterator iterator = preResultListeners.iterator(); <br>    iterator.hasNext();) { <br>    PreResultListener listener = (PreResultListener) iterator.next(); <br>    String _profileKey="preResultListener: "; <br>    try { <br>    UtilTimerStack.push(_profileKey); <br>    listener.beforeResult(this, resultCode); <br>    } <br>    finally { <br>    UtilTimerStack.pop(_profileKey); <br>    } <br>    } <br>    } <br>    if (proxy.getExecuteResult()) { <br>    executeResult(); <br>    } <br>    executed = true; <br>    } <br>    return resultCode; <br>    } <br>    finally { <br>    UtilTimerStack.pop(profileKey); <br>    } <br>} <br><br>在上述代码实现递归调用截拦器是由Interceptor 类来实现的. <br><br>publicinterface Interceptor extends Serializable { <br>  void destroy(); <br>  void init(); <br>  String intercept(ActionInvocation invocation) throws Exception; <br>} <br><br>所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用 invocation.invoke(),invoke()方法中蓝色代码会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归. <br>调用流程如下: <br><br> 如果截拦器全部执行完毕,则调用invokeActionOnly()方法执行Action,invokeActionOnly()方法基本没做什么工作,只调用了invokeAction()方法。 <br><br>public String invokeActionOnly() throws Exception { <br>    return invokeAction(getAction(), proxy.getConfig()); <br>} <br><br> DefaultActionInvocation.invokeAction()方法实现Action的调用. <br><br>protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { <br>        String methodName = proxy.getMethod(); <br>        if (LOG.isDebugEnabled()) { <br>            LOG.debug("Executing action method = " + actionConfig.getMethodName()); <br>        } <br>        String timerKey = "invokeAction: "+proxy.getActionName(); <br>        try { <br>            UtilTimerStack.push(timerKey); <br>            Method method; <br>            try { <br>                method = getAction().getClass().getMethod(methodName, new Class[0]); <br>            } catch (NoSuchMethodException e) { <br>                try { <br>                    String altMethodName = "do" + <br>             methodName.substring(0, 1).toUpperCase() + methodName.substring(1); <br>                    method = getAction().getClass().getMethod(altMethodName, <br>new Class[0]); <br>                } catch (NoSuchMethodException e1) { <br>                    throw e; <br>                } <br>            } <br>            Object methodResult = method.invoke(action, new Object[0]); <br>            if (methodResult instanceof Result) { <br>            this.result = (Result) methodResult; <br>            return null; <br>            } else { <br>            return (String) methodResult; <br>            } <br>        } catch (NoSuchMethodException e) { <br>            throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + ""); <br>        } catch (InvocationTargetException e) { <br>            Throwable t = e.getTargetException(); <br>            if (actionEventListener != null) { <br>                String result = actionEventListener.handleException(t, getStack()); <br>                if (result != null) { <br>                    return result; <br>                } <br>            } <br>            if (t instanceof Exception) { <br>                throw(Exception) t; <br>            } else { <br>                throw e; <br>            } <br>        } finally { <br>            UtilTimerStack.pop(timerKey); <br>        } <br>} <br><br>由这句Object methodResult = method.invoke(action, new Object[0]);可以看出,最后通过反射实现了Action的执行方法的调用。 <br><br> 接着返回invoke()方法,判断executed是否为false.如果是则调用了在PreResultListener中的定义的一些执行Result前的操作. <br> 接着根据配置文件中的设置执行Result.其执行方法为executeResult()方法. <br><br>private void executeResult() throws Exception { <br>        result = createResult(); <br>        String timerKey = "executeResult: "+getResultCode(); <br>        try { <br>            UtilTimerStack.push(timerKey); <br>            if (result != null) { <br>                result.execute(this); <br>            } else if (resultCode != null &amp;&amp; !Action.NONE.equals(resultCode)) { <br>                throw new ConfigurationException("No result defined for action " <br>+ getAction().getClass().getName() <br>                        + " and result " + getResultCode(), proxy.getConfig()); <br>            } else { <br>                if (LOG.isDebugEnabled()) { <br>                    LOG.debug("No result returned for action " <br>+getAction().getClass().getName() <br>+" at "+proxy.getConfig().getLocation()); <br>                } <br>            } <br>        } finally { <br>            UtilTimerStack.pop(timerKey); <br>        } <br>} <br> 然后,返回到dispatcher.serviceAction()方法,完成调用. <br>Struts2流程: <br></p>
<p> </p>
</div>
<p> </p>
9 楼 lishijia 2010-01-10  
看起来确实费劲。。。
8 楼 JArcher 2010-01-10  
建议图文结合。。。
7 楼 free_zk 2010-01-10  
实在是没有办法看了
6 楼 gogole_09 2010-01-09  
zhxing 写道
 接着调用UtilTimerStack.push()方法,但是搞不明这是有什么用的。

这个是用来记录时间的,方便检查执行效率

ps,lz 是不是转载的,排版难看。



这个类是是WebWork中的实用类,计算一个方法体的执行时间的
做性能参考之用
看下API的 JAVADOC有说明的。
......
pop(String name) 
 /*End a preformance profiling with the name given.*/
push(String name) 
 /*Create and start a performance profiling with the name given.*/



PS: 排版确实难看了点, 估计很少有人能完整的看完楼主的这个文章, 太费力了点……
5 楼 JustDoNow 2010-01-08  
如果是转载的话也太不用心了
不如给个连接算了
4 楼 橘子洲头 2010-01-08  
太多代码了,直接画个流程图说明会更清晰很多
3 楼 yhailj 2010-01-08  
一看就知道是转载的, 貌似还有乱码. 转载前请先注明地址!!!

哎...
2 楼 zhxing 2010-01-08  
 接着调用UtilTimerStack.push()方法,但是搞不明这是有什么用的。

这个是用来记录时间的,方便检查执行效率

ps,lz 是不是转载的,排版难看。
1 楼 jordan421 2010-01-07  
贴下漫漫看好了

相关推荐

    struts2流程与流程图

    一个请求在Struts 2框架中的处理大概分为以下几个步骤。  客户端提交一个(HttpServletRequest)请求,如上文在浏览器中输入 http://localhost: 8080/bookcode/ch2/Reg.action就是提交一个(HttpServletRequest)...

    struts调用流程总结,适用于初学者.

    struts调用流程总结,适用于初学者.本人学的过程中总结的.如果有什么欠佳的还往提出..呵呵

    Struts2的工作原理和流程

    2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin) 3 接着FilterDispatcher被调用,...

    struts工作流程.doc

    1. 客户端发送一个请求,...2. ActionSevlet首先调用struts配置文件(struts-config.xml),如果有表单提交,需要自动填充ActionForm Bean(找struts配置文件的),根据请求分派给相应的Action(找struts配置文件的)。

    Struts2入门教程(全新完整版)

    一、准备工作及实例 3 1.解压struts-2.1.6-all.zip 3 2.六个基本包 3 3.初识struts2配置文件 4 ... 下面对struts2的基本执行流程作一简要说明,此流程说明可以结合官方提供的struts2结构图来看: 60

    配置Struts 2开发环境,了解和熟悉Struts 2的开发流程、了解和熟悉Struts2标签的使用方法.rar

    2.配置struts.xml文件; 3.掌握在视图中访问Action的方法. 4. 掌握在视图中多方法调用同一个Action的方法 5.掌握常用标签textfield、radio、paaaword、checkboxlist、select、data的使用方法,实现页面与Action的交互...

    struts2开发文档

    struts2的所有知识点 流程 原理 一个请求在Struts2框架中的处理大概分为以下几个步骤: 1 客户端初始化一个指向Servlet容器(例如Tomcat)的请求; 2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个...

    struts2课程笔记

    struts2知识点:处理流程,基本配置,与servlet的API解耦,结果类型,通配符和动态方法的调用,类型转换,文件上传,拦截器,表单验证,国际化,OGNL表达式,UI标签,模型驱动,防止表单重复提交,项目练习知识点:...

    struts2 详解文档

    Struts 2的处理流程与Action的管理方式 为应用指定多个配置文件 动态方法调用和使用通配符定义action 请求参数接收 自定义类型转换器 全局类型转换器 访问或添加几个属性 文件上传 多文件上传 自定义拦截...

    深入浅出Struts2(附源码)

    本书是广受赞誉的Struts 2优秀教程,它全面而深入地阐述了Struts 2的各个特性,并指导开发人员如何根据遇到的问题对症下药,选择使用最合适的特性。作者处处从实战出发,在丰富的示例中直观地探讨了许多实用的技术,...

    项目功能流程图(struts项目)

    本流程图的结构是树形结构。节点分jsp、action和类文件三种。原理是由jsp开始。指向在jsp中要调用的action方法。每个要调用的action方法都是一个节点。而action方法又指向所要调用的类方法。所有要调用的类方法都是...

    Struts2请求处理流程及源码分析

    b)根据Web.xml配置,请求首先经过ActionContextCleanUp过滤器,其为可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助(SiteMeshPlugin),主要清理当前线程的ActionContext和Dispatcher;c)请求经过插件...

    Struts 通俗易懂

    一个struts入门的简单流程图。 next1: 根据你提交表单的申请JSP服务器容器把HelloWorld.do映射到 next2: JSP服务器容器把的HelloWorld.do指到的path="/HelloWorld"; next3: struts控制器根据name="HelloForm"找中...

    struts2 标签库 帮助文档

    Struts 2 标签库(文档手册) Tags-API-CLSW-JSP &lt;%@ taglib prefix="s" uri="/struts-tags" %&gt; 就能使用struts2.0的标签库 下面就介绍每个标签的具体应用实例说明:按字母排列 A: 1. 2. &lt;s:a href=""&gt;&lt;/s:a&gt;-...

    struts2+freemarker学习案例

    自己写的一个struts2+freemarker例子 供大家研究一下,这两者互相调用的流程机制..

    struts 架构剖析

    了解Struts2和XWork的关系,以及各自的职责 熟悉Struts2的体系结构 了解Struts2框架的调用流程

    深入浅出Struts 2 .pdf(原书扫描版) part 1

    2.2 Struts的动作处理流程 15 2.3 拦截器 17 2.4 Struts配置文件 18 2.4.1 struts.xml文件 19 2.4.2 struts.properties文件 26 2.5 Struts应用程序示例 26 2.5.1 部署描述文件和Struts配置文件 27 2.5.2 动作类 28 ...

Global site tag (gtag.js) - Google Analytics