structs2源码解读之解析package标签 

    上面讨论过,在创建Dispacher对象时,调用dispacher.init()方法完成初始化,在这个方法中先创建各种配置文件的解析器(ConfigurationProvider),然后循环遍历这些解析器的register()方法解析各个配置文件。

 for (final ContainerProvider containerProvider : providers)
        {
            containerProvider.init(this);
            containerProvider.register(builder, props);
        }

但是这里没有并没有解析到package标签,因为package标签内容比较多,所以struct2另外用一个方法解析这个标签。在上面遍历解析器解析配配置文件后,再循环遍历,如果这个解析器是packageProvider的实例,才调用loadpackages()方法解析。

 for (final ContainerProvider containerProvider : providers)
            {
           //loadpackages()是packageProvider接口提供的方法,这里处理找不到方法的异常
                if (containerProvider instanceof PackageProvider) {
                   //inject()方法表示交给容器管理
                    container.inject(containerProvider);
                    //解析package标签的方法
                    ((PackageProvider)containerProvider).loadPackages();
                    //把这个解析器放到一个list集合中
                    packageProviders.add((PackageProvider)containerProvider);
                }
            }

下面我们就来探讨下详细的解析过程。

一、package标签

    解析之前,我们来看看package有什么属性

代码清单:package标签
    <package name="struts-default" abstract="true" extends="" namespace="" externalReferenceResolver="">
        <result-types>
            <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
 
        </result-types>

        <interceptors>
            <interceptor name="exception" class=""/>

            <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
            </interceptor-stack>
       </interceptors>
        <action name="verify_*" class="verifyAction" method="{1}">
       
 
	</action>

        <default-interceptor-ref name="defaultStack"/>

        <default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
    </package>


二、package标签解析过程

    loadpackage()是相应解析器解析package标签的方法。我们来看看struct2的xml解析器

StrutsXmlConfigurationProvider的loadpackage()方法

public void loadPackages() {
        ActionContext ctx = ActionContext.getContext();
        ctx.put(reloadKey, Boolean.TRUE);
         //父类XmlConfigurationProvider的loadpackage()方法
        super.loadPackages();
    }

从这里看到,struct2其实还是调用了xwork的解析方法。所以说struct2大部分工作都是对xwork进行封装。我们来看看XmlConfigurationProvider的loadpackage()方法

    public void loadPackages() throws ConfigurationException {
        //一个存放节点的集合,比如一个子点继承了另外一个节点,那么就把这个节点放到这个集合中,然后再循环遍历这个集合解析继承的那个节点
        List<Element> reloads = new ArrayList<Element>();
        //循环document对象
        for (Document doc : documents) {
           //获得根节点<struts>
            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();
                    //如果当前子节点的名称为package
                    if ("package".equals(nodeName)) {
                      //把package标签里面的属性封装到packageConfig对象中
                        PackageConfig cfg = addPackage(child);
                        //如果配置了extends,而extends里面的package还没有解析,则把这个节         点放到一个list集合中
                        if (cfg.isNeedsRefresh()) {
                            //把package节点放到一个list集合中
                            reloads.add(child);
                        }
                    }
                }
            }
            //这是个什么也没有做的方法,用于扩展
            loadExtraConfiguration(doc);
        }
        //如果配置了extends,而extends里面的package还没有解析的,则重新解析,特别注意,上面解析的时候,已经把顶级父类的package封装成了PackageConfig 对象,所以这里某些package可能会找到父类
        if (reloads.size() > 0) {
            //解析继承的节点
            reloadRequiredPackages(reloads);
        }
        for (Document doc : documents) {
            loadExtraConfiguration(doc);
        }
        
        documents.clear();
        configuration = null;
    }

从这个方法我们大概知道了解析package标签的流程:获得document对象package节点的信息,把它封装到一个packageConfig对象中,如果信息配置有extends,则再解析继承的那个package标签。那么他是如何知道是否配置有extends的呢 ?在addPackage()方法中

 String parent = packageElement.getAttribute("extends");
 //如果extends属性不为空
if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { 
             //获取继承package的PackageConfig对象,因为extends可配置多个,所以这里是list
            List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
            //因为继承的那个package也单独配置到了其他的xml文件中,所以在解析那个配置文件的时候,会把那个配置文件的package标签封装到了PackageConfig对象中并缓存起来,上面那个方法就是从缓存中取出这个PackageConfig对象,因为解析是循环遍历的,不能控制顺序,所以这里设置一个标记boolen needsRefresh,如果找不到这个对象,则把这个标记置为true,当解析一个package标签的时候,发现他配置了extends,而继承的那个对象还没解析(needsRefresh=true),那么就有了上面的把这个节点放到一个集合中,然后重新解析
            if (parents.size() <= 0) {
                cfg.needsRefresh(true);
            } else {
                 //如果父package已经解析了,则把这个PackageConfig设置到当前的PackageConfig的parents属性中
                cfg.addParents(parents);
            }
        }

那么它又是如何重新解析的呢?

   private void reloadRequiredPackages(List<Element> reloads) {
        //如果需要重新解析
        if (reloads.size() > 0) {
            List<Element> result = new ArrayList<Element>();
            //循环遍历需要解析的节点
            for (Element pkg : reloads) {
                //把节点信息封装成PackageConfig 
                PackageConfig cfg = addPackage(pkg);
                //如果解析的节点又继承了其他的package
                if (cfg.isNeedsRefresh()) {
                    //把这个节点放到一个list集合中,后面再重新解析
                    result.add(pkg);
                }
            }
            //再重新解析,注意的是这个集合必须比之前的集合的长度要小,也就是说,这个节点必须有一个或多个是不需要重新解析的,也就是这个节点是其他某个字点的父类
            if ((result.size() > 0) && (result.size() != reloads.size())) {
                reloadRequiredPackages(result);
                return;
            }
            // 如果有节点找不到父类,则抛出错误信息:找不到父类
            if (result.size() > 0) {
                for (Element rp : result) {
                    String parent = rp.getAttribute("extends");
                    if (parent != null) {
                        List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
                        if (parents != null && parents.size() <= 0) {
                            LOG.error("Unable to find parent packages " + parent);
                        }
                    }
                }
            }
        }
    }

    综上所述,package的解析过程是:获得document对象的package标签,把每个package标签的信息封装到packageConfig对象,如果这个标签配置了extends属性,而extends里面配置的package还没有做解析封装成packageConfig,则做NeedsRefresh=true标记,因为如果配置了extends属性,要设置packageConfig.parents=ParentpackageConfigs才算封装完整,又因为解析是无序的,这个ParentpackageConfigs不知道什么时候解析出来,所以循环遍历过一次之后,对于做了NeedsRefresh=true标记的packageConfig(把它们放到一个list集合中),需要再循环遍历一次,因为在第一轮循环遍历的时候,已经把没有配置extends的package的封装成了packageConfig对象,所以第二轮遍历的时候,有些package可能会找到父类的packageConfig,对于在第二轮循环遍历还没找到的,则再进行第三次循环遍历(第二次遍历的有可能是第三次遍历节点的父package),以此递归,到最后还是没有找到 的,则抛出错误信息。


三、解析package标签

    上面我们分析了解析package标签的流程,无论是解析还是重新解析,都是调用addPackage()把package标签封装成packageConfig对象,所以重点还是在这个方法,下面我们就来探讨下上面的流程到底是如何实现的。

  protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
        //1.获取package的属性信息,如namespace/extends
        PackageConfig.Builder newPackage = buildPackageContext(packageElement);

        //因为设置了extends而父类还没解析的,后面需要重新解析,所以解析完package属性后,直接返回一个没有其他子标签信息的packageConfig对象
        if (newPackage.isNeedsRefresh()) {
            return newPackage.build();
        }
        //否则的话继续解析当前package的子标签
        // 2.解析resultTypes标签
        addResultTypes(newPackage, packageElement);

        // 解析拦截器Interceptors标签
        loadInterceptors(newPackage, packageElement);

        // 解析default-interceptor-reference标签
        loadDefaultInterceptorRef(newPackage, packageElement);

        //解析default-class-ref标签
        loadDefaultClassRef(newPackage, packageElement);

        // 解析GlobalResults标签
        loadGlobalResults(newPackage, packageElement);

        // 解析GobalExceptionMappings标签
        loadGobalExceptionMappings(newPackage, packageElement);

        //解析aciton标签
        NodeList actionList = packageElement.getElementsByTagName("action");

        for (int i = 0; i < actionList.getLength(); i++) {
            Element actionElement = (Element) actionList.item(i);
            addAction(actionElement, newPackage);
        }
        //解析default-action-reference标签
        loadDefaultActionRef(newPackage, packageElement);
        //3.实例化一个PackageConfig对象 
        PackageConfig cfg = newPackage.build();
        //4.把PackageConfig放到一个名为packageContexts的map中缓存
        configuration.addPackageConfig(cfg.getName(), cfg);
        return cfg;
    }

    从上面我们大概了解了这个方法的事情:

    (1)处理package的属性

<package name="struts-default" abstract="true" extends="" namespace="" externalReferenceResolver="">

    (2)用不同的方法解析不同的标签,并把他们封装到不同的对象

    (3)把这些对象封装到一个packageConfig对象中

    (4)把这个对象缓存起来

3.1.  缓存对象

     我们先来解析下这个最简单的缓存对象。在part2中我们讨论package解析流程的时候说到,如果当前的package标签配置了extends,会在缓存中找是否是否已经解析了这个package,这个缓存就是在这里发生的。我们来看下这个addPackageConfig()方法

   protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
   public void addPackageConfig(String name, PackageConfig packageContext) {
        //查看缓存中是否已经存在
        PackageConfig check = packageContexts.get(name);
        if (check != null) {
           //日志或者是异常信息,略
        }
        //如果不存在,则缓存到一个map中起来
        packageContexts.put(name, packageContext);
    }

    这个packageContext是一个map类型,它是Configuration的一个属性,通过Configuration.getPackageConfig(string)方法就可以取出这个map里面的相应的值

 public PackageConfig getPackageConfig(String name) {
        return packageContexts.get(name);
    }

3.2.实例化一个packageConfig

    接着我们再来分析另外一个较为简单的,就是实例化packageConfig。通过我们之前解析配置文件的经验来说,封装对象,无非就是把一堆属性设置到一个对象的属性中去。这里也不例外,我们来看看

newPackage.build()这个方法

 private PackageConfig target
 public PackageConfig build() {
    //设置属性
    target.actionConfigs = Collections.unmodifiableMap(target.actionConfigs);
    target.globalResultConfigs = Collections.unmodifiableMap(target.globalResultConfigs);
    target.interceptorConfigs = Collections.unmodifiableMap(target.interceptorConfigs);
    target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs);
    target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs);
    target.parents = Collections.unmodifiableList(target.parents);
    PackageConfig result = target;
    //实例化一个PackageConfig 对象
    target = new PackageConfig(result);
    return result;
 }

    这里也论证了我们的想法,就是把acitonConfigs和resultTypeConfigs等等对象设置到packageConfig的属性中去(acitonConfigs对象封装了action标签的信息,resultTypeConfigs对象封装了resultType的信息),从而得到了有效统一的管理。需要特别注意的是,这里用到了构造者的实例方法,newPackage是一个PackageConfig.Builder类型,Builder是一个内部类,通过内部类的一个方法build()实例化本身对象,这个方法可用于多个参数的构造方法。


3.3.获取package属性

<package name="struts-default" abstract="true" extends="" namespace="" externalReferenceResolver="">
<package>

   xml标签都是由属性和子标签组成。我们先来看下struct2是如何处理package属性的

 protected PackageConfig.Builder buildPackageContext(Element packageElement) {
       //获取属性值
        String parent = packageElement.getAttribute("extends");
        String abstractVal = packageElement.getAttribute("abstract");
        boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue();
        String name = StringUtils.defaultString(packageElement.getAttribute("name"));
  String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace"));

        if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) {
        //如果配置了externalReferenceResolver则抛出异常
        }
        
        //创建一个Builder对象,把属性值封装到Builder对象中
        PackageConfig.Builder cfg = new PackageConfig.Builder(name)
                .namespace(namespace)
                .isAbstract(isAbstract)
                .location(DomHelper.getLocationObject(packageElement));
         //如果配置了extends属性

        if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { 
              //从缓存中找下是否已经有父类的PackageConfig对象
      List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
              //如果没有,标记needsRefresh=ture
            if (parents.size() <= 0) {
                cfg.needsRefresh(true);
            } else {
               //如果有,设置parents属性
                cfg.addParents(parents);
            }
        }
        return cfg;
    }

     这里主要都是创建了一个Builder对象,在创建Builder对象过程中也设置了相应的默认值,如

 public Builder namespace(String namespace) {
             //namespace如何不设置,默认为空字符串,注意空字符串不是null
            if (namespace == null) {
                target.namespace = "";
            } else {
                target.namespace = namespace;
            }
            return this;
        }


3.4.封装子标签属性

   正如上面所说,封装子标签属性无非就是把标签配置的属性值设置到相应的对象中。下面我们就举一两个例子做解析

(1)Default-Interceptor-Ref标签

 protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) {
      //获取Default-Interceptor-Ref标签
       NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref");

        if (resultTypeList.getLength() > 0) {
            Element defaultRefElement = (Element) resultTypeList.item(0);
             //把name中的属性值设置到PackageConfig的defaultInterceptorRef属性中
            packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name"));
        }
    }

(2)Interceptors标签

 protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException {
        NodeList interceptorList = element.getElementsByTagName("interceptor");

        for (int i = 0; i < interceptorList.getLength(); i++) {
           //获取Interceptors标签的属性值
            Element interceptorElement = (Element) interceptorList.item(i);
            String name = interceptorElement.getAttribute("name");
            String className = interceptorElement.getAttribute("class");

            Map<String, String> params = XmlHelper.getParams(interceptorElement);
            //封装到InterceptorConfig 对象
            InterceptorConfig config = new InterceptorConfig.Builder(name, className)
                    .addParams(params)
                    .location(DomHelper.getLocationObject(interceptorElement))
                    .build();
            //把InterceptorConfig 对象设置到PackageConfig的interceptorConfigs属性中,这个interceptorConfigs是一个map类型
            context.addInterceptorConfig(config);
        }
        //封装 InterceptorStacks信息
        loadInterceptorStacks(element, context);
    }

(3)action标签

   protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
       //获取action标签属性值
        String name = actionElement.getAttribute("name");
        String className = actionElement.getAttribute("class");
        String methodName = actionElement.getAttribute("method");
        Location location = DomHelper.getLocationObject(actionElement);
        //如果method不设,默认为null
        methodName = (methodName.trim().length() > 0) ? methodName.trim() : null;

        if (StringUtils.isEmpty(className)) {

        } else {
           //校验action的name和class属性,例如是否是public之类的
            if (!verifyAction(className, name, location)) {
  
            }
        }
         //封装result标签属性到ResultConfig对象
        Map<String, ResultConfig> results;
        try {
            results = buildResults(actionElement, packageContext);
        }
        //封装interceptor-ref标签属性到InterceptorStackConfig对象
        List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);标签
        //封装exception-mapping属性到ExceptionMappingConfig对象
        List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
         //封装配置信息到ActionConfig 对象
        ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className)
                .methodName(methodName)
                .addResultConfigs(results)
                .addInterceptors(interceptorList)
                .addExceptionMappings(exceptionMappings)
                .addParams(XmlHelper.getParams(actionElement))
                .location(location)
                .build();
        //把ActionConfig 对象设置到PackageConfig的ActionConfig 属性中,这个ActionConfig 是一个map类型      
        packageContext.addActionConfig(name, actionConfig);
    }

    从上面三个例子,我们进一步理解了struct2解析配置文件的过程无非就是获取标签,获取标签属性,把属性设置到相应的对象属性中去,然后把这个对象再设置到packageConfig属性中去,从而返回了一个包含package标签信息的packageConfig对象,很简单,是不是?下面我再举一个封装result的例子来结束这个专题的探讨吧。

(4)封装result标签

protected Map<String, ResultConfig> buildResults(Element element, PackageConfig.Builder packageContext) {
        //得到所有result子节点
        NodeList resultEls = element.getElementsByTagName("result");
        //存放每一个result的集合
        Map<String, ResultConfig> results = new LinkedHashMap<String, ResultConfig>();
        //循环遍历
        for (int i = 0; i < resultEls.getLength(); i++) {
            Element resultElement = (Element) resultEls.item(i);
            if (resultElement.getParentNode().equals(element) || resultElement.getParentNode().getNodeName().equals(element.getNodeName())) {
                //获取result属性值
                String resultName = resultElement.getAttribute("name");
                String resultType = resultElement.getAttribute("type");

                // name默认值为success
                if (StringUtils.isEmpty(resultName)) {
                    resultName = Action.SUCCESS;
                }

                //type的默认值,父类中设置的DefaultResultType,如父package(struts-default)中设置的<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
                if (StringUtils.isEmpty(resultType)) {
                    resultType = packageContext.getFullDefaultResultType();

                }

               //把type封装到ResultTypeConfig 对象
                ResultTypeConfig config = packageContext.getResultType(resultType);
                String resultClass = config.getClazz();

        
                //result的Params属性
                Map<String, String> resultParams = XmlHelper.getParams(resultElement);
                if (resultParams.size() == 0) {             
                   if (resultElement.getChildNodes().getLength() >= 1) {
                        resultParams = new LinkedHashMap<String, String>();
                        String paramName = config.getDefaultResultParam();
                        if (paramName != null) {
                            StringBuilder paramValue = new StringBuilder();
                    for (int j = 0; j < resultElement.getChildNodes().getLength(); j++) {
              f (resultElement.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) {
                String val = resultElement.getChildNodes().item(j).getNodeValue();
                                    if (val != null) {
                                        paramValue.append(val);
                                    }
                                }
                            }
                            String val = paramValue.toString().trim();
                            if (val.length() > 0) {
                                //把params值放到一个map中
                                resultParams.put(paramName, val);
                            }
                        } 
                    }
                }
            //把result和resultType的parms放在一起
           Map<String, String> params = new LinkedHashMap<String, String>();
           Map<String, String> configParams = config.getParams();
           if (configParams != null) {
                    params.putAll(configParams);
            }
            params.putAll(resultParams);
            //封装result属性到ResultConfig对象中
            ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass)
                        .addParams(params)
                        .location(DomHelper.getLocationObject(element))
                        .build();
              //把每个ResultConfig对象放到一个map集合中
                results.put(resultConfig.getName(), resultConfig);
            }
        }

        return results;
    }


四、总结

    这篇博文我们主要分析了struct2是如何解析xml的package标签的:读取document对象获得package节点,然后获取package的属性值,并实例化一个packageConfig.Builder对象。然后判断是否配置了extends属性,如果配置了,判断配置的package是否已经实例化,如果已经实例化则解析子标签,把子标签属性封装到各个对象中,如action标签封装到actionConfig对象中,然后把这个对象中设置到packageConfig对象中返回,否则直接返回一个packageConfig返回,并设置一个标记NeedsRefresh=true;如果没有配置,则直接解析子标签。解析完一轮后,循环遍历标记了的packageConfig,再重新解析一次,一次类推,最后把所有package解析出来。

   致此,我们就已经把解析配置文件的工作都解析完了,下篇博文将讨论把这些封装了不同配置信息的对象组合起来,搭建我们struct2的开发环境。