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的开发环境。