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标签解析过程

    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
 reloads = new ArrayList
();        //循环document对象        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();                    //如果当前子节点的名称为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
 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
 reloads) {        //如果需要重新解析        if (reloads.size() > 0) {            List
 result = new ArrayList
();            //循环遍历需要解析的节点            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
 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标签        loadDefaultCla***ef(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的属性

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

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

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

3.1.  缓存对象

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

   protected Map
 packageContexts = new LinkedHashMap
();   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属性

   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
 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
 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
 results;        try {            results = buildResults(actionElement, packageContext);        }        //封装interceptor-ref标签属性到InterceptorStackConfig对象        List
 interceptorList = buildInterceptorList(actionElement, packageContext);标签        //封装exception-mapping属性到ExceptionMappingConfig对象        List
 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
 buildResults(Element element, PackageConfig.Builder packageContext) {        //得到所有result子节点        NodeList resultEls = element.getElementsByTagName("result");        //存放每一个result的集合        Map
 results = new LinkedHashMap
();        //循环遍历        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)中设置的
                if (StringUtils.isEmpty(resultType)) {                    resultType = packageContext.getFullDefaultResultType();                }               //把type封装到ResultTypeConfig 对象                ResultTypeConfig config = packageContext.getResultType(resultType);                String resultClass = config.getClazz();                        //result的Params属性                Map
 resultParams = XmlHelper.getParams(resultElement);                if (resultParams.size() == 0) {                                if (resultElement.getChildNodes().getLength() >= 1) {                        resultParams = new LinkedHashMap
();                        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
 params = new LinkedHashMap
();           Map
 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的开发环境。