您的位置:首页 > 其它

structs2源码解读(6)之解析package标签

2015-11-07 13:45 519 查看
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的开发环境。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息