Struts2 源码分析——配置管理之PackageProvider接口
本章简言 |
上一章讲到关于ContainerProvider的知识。让我们知道struts2是如何注册相关的数据。也知道如何加载相关的配置信息。本章笔者将讲到如何加载配置文件里面的package元素节点 。如struts.xml文件里面的package节点。那么为什么要分开来讲呢?按道理的话,都是放在上一章中一起讲不是更好吗?关键点在于笔者也不明白strtus2的作者为什么会这样子设计,把关于加载package元素节点信息的工作独立出来。而他的接口便是PackageProvider接口。但是不管如何他的重要性不用笔者多讲。
PackageProvider的开始 |
从前面几章中我们了解到了一点:想知道如何加载相关配置文件就必须去找StrutsXmlConfigurationProvider类和XmlConfigurationProvider类。而StrutsXmlConfigurationProvider类和XmlConfigurationProvider类是在Dispatcher类的init_TraditionalXmlConfigurations方法里面被调用。代码如下。
Dispatcher类:
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.addContainerProvider(createXmlConfigurationProvider(file, false));
} else {
configurationManager
.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
}
} else {
throw new IllegalArgumentException("Invalid configuration file name");
}
}
}
看了上面的代码。相信读者也明白struts2会先去找过滤参数(initParams)里面是否有指定要去加载哪些配置文件。如果没有的话,就用DEFAULT_CONFIGURATION_PATHS常量的值来加载。即是用"struts-default.xml,struts-plugin.xml,struts.xml"来解析加载。看样子不用笔者多讲大家都明白了。加载相关配置文件的代码其实就在这里开始发生的。然后就是进行供应者注册的工作。(相关的内容在《Struts2 源码分析——配置管理之ContainerProvider接口》也有讲到) 这里笔者想提一下上面提到的struts-plugin.xml配置文件。这个置配文件是在插件包里面。如struts2-convention-plugin-2.5.2.jar等。也就是说XmlConfigurationProvider类也有加载插件包的配置信息功能。这一点在XmlConfigurationProvider类的loadConfigurationFiles方法里面体现的很明显。而loadConfigurationFiles方法就是用于初始化时候,加载对应的配置文件。看一下代码吧。
XmlConfigurationProvider类:
public void init(Configuration configuration) {
this.configuration = configuration;
this.includedFileNames = configuration.getLoadedFileNames();
loadDocuments(configFileName);
}
XmlConfigurationProvider类 :
private void loadDocuments(String configFileName) {
try {
loadedFileUrls.clear();
documents = loadConfigurationFiles(configFileName, null);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new ConfigurationException("Error loading configuration file " + configFileName, e);
}
}
XmlConfigurationProvider类的loadConfigurationFiles方法:
Iterator<URL> urls = null;
InputStream is = null; IOException ioException = null;
try {
urls = getConfigurationUrls(fileName);//获得配置文件所以在的URLS。就是找到哪里包里面有fileName值的URLS
} catch (IOException ex) {
ioException = ex;
} if (urls == null || !urls.hasNext()) {
if (errorIfMissing) {
throw new ConfigurationException("Could not open files of the name " + fileName, ioException);
} else {
LOG.trace("Unable to locate configuration files of the name {}, skipping", fileName);
return docs;
}
}
笔者没有把关于loadConfigurationFiles方法的代码他全部贴出来。只是贴出一部分。主要是想让读者知道。加载插件包的配置文件是如何进行的。为了什么要讲这个呢?reloadContainer方法里面在加载package元素的时候,进行了俩个部分的加载工作。一分部是加载当前提供的供应者。另一部分就是加载插件包里面的供应者。所以就必须知道原来还有插件包里面的供应者。代码如下:
ActionContext oldContext = ActionContext.getContext();
try { setContext(bootstrap);//创建一个Action上下文
container = builder.create(false);//新建一个Container容器
setContext(container);//创建一个Action上下文
objectFactory = container.getInstance(ObjectFactory.class); // 处理用户配置里面的供应者,如果是PackageProvider,就是加载对应的package元素信息
for (final ContainerProvider containerProvider : providers)
{
if (containerProvider instanceof PackageProvider) {
container.inject(containerProvider);
((PackageProvider)containerProvider).loadPackages();
packageProviders.add((PackageProvider)containerProvider);
}
} // 然后处理当前插件供应者,加载对应的package元素信息
Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
for (String name : packageProviderNames) {
PackageProvider provider = container.getInstance(PackageProvider.class, name);
provider.init(this);
provider.loadPackages();
packageProviders.add(provider);
} rebuildRuntimeConfiguration();//新建运行时候,用的配置
} finally {
if (oldContext == null) {
ActionContext.setContext(null);
}
}
PackageProvider的内容 |
相信到这里,大家都知道加载package元素在哪里开始执行的。而关于加载package元素中却用到很多东西。让笔者一个个讲给大家听吧。首先让我们一下XmlConfigurationProvider类的loadPackages方法吧。这里才是正真加载工作。代码如下
XmlConfigurationProvider类:
public void loadPackages() throws ConfigurationException {
List<Element> reloads = new ArrayList<Element>();
verifyPackageStructure(); 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 ("package".equals(nodeName)) {//判断是否是package元素。
PackageConfig cfg = addPackage(child);//如果是增加package元素
if (cfg.isNeedsRefresh()) {//判断是否需要重新加载
reloads.add(child);
}
}
}
}
loadExtraConfiguration(doc);
} if (reloads.size() > 0) {
reloadRequiredPackages(reloads);
} for (Document doc : documents) {
loadExtraConfiguration(doc);
} documents.clear();
declaredPackages.clear();
configuration = null;
}
看到了上面的代码,大家都知道相关增加package元素的工作在addPackage方法里面进行的。而方法最后会返回一个PackageConfig类。PackageConfig类就是用于存放package元素信息的。为了方便读者学习,笔者希望读者能了解一下struts-2.5.dtd这个文件。笔者现在不清楚有多少人了解过DTD的相关语法。或许很多人不知道DTD是什么东东。那么为什么要了解这个DTD文件呢?让我们看一下DTD文件里面的一段代码吧。
<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
<!ATTLIST package
name CDATA #REQUIRED
extends CDATA #IMPLIED
namespace CDATA #IMPLIED
abstract CDATA #IMPLIED
strict-method-invocation (true|false) "true"
>
从上面的DTD信息我们很快了解到package元素节点到底有些什么内容。同时了解到package元素有哪里子节点。通过上面的信息在和PackageConfig类的成员变量进行对比学习的话,就比较清楚的知道为什么会有这个成员变量了。所以让我们看一段关于PackageConfig类的代码。如下
protected Map<String, ActionConfig> actionConfigs;//action的配置信息
protected Map<String, ResultConfig> globalResultConfigs;//结果的配置信息
protected Set<String> globalAllowedMethods;//公共允许的方法
protected Map<String, Object> interceptorConfigs;//拦截器
protected Map<String, ResultTypeConfig> resultTypeConfigs;//结果类型的配置信息
protected List<ExceptionMappingConfig> globalExceptionMappingConfigs;//异常的配置信息
protected List<PackageConfig> parents;//package元素的父配置信息
protected String defaultInterceptorRef;//默认的拦截器
protected String defaultActionRef;//默认的action
protected String defaultResultType;//默认的result信息
protected String defaultClassRef;
protected String name;//名字
protected String namespace = "";//命名空间
protected boolean isAbstract = false;//是否为抽象
protected boolean needsRefresh;//需要重新刷新
protected boolean strictMethodInvocation = true;
让笔者简单的讲解一下关于每个变量的作用吧。如下
1.Map<String, ActionConfig> actionConfigs:用于存放action的配置信息。我们都知道一个package可以对应多的action配置。
2.Map<String, ResultConfig> globalResultConfigs:用于存入对应的公共结果。也许有一种情况,那就是多个action共用一个结果。
3.Set<String> globalAllowedMethods:就是action允许被调用的方法。在struts-default.xml配置文件里面设置默认的值:execute,input,back,cancel,browse,save,delete,list,index。
4.Map<String, Object> interceptorConfigs:用于存放当前package元素的拦截器。对于拦截器的概念的话。后面的章节会讲到。
5.Map<String, ResultTypeConfig> resultTypeConfigs:用于存放action返回的结果类型。
6.List<ExceptionMappingConfig> globalExceptionMappingConfigs:用于存放action发生异常的异常配置。
7.ist<PackageConfig> parents:用于存放当前package元素的父package元素的信息。
8.String defaultActionRef:标示当前package元素的默认action。
9.String defaultResultType:标示当前action返回的默认结果类型。
10.String defaultClassRef:action类默认的父类。
11.String name:package元素的名称
12.String namespace :package元素的命名空间
13.boolean isAbstract:package元素是否为抽象
14.boolean needsRefresh:标示是否需要重新刷新。
15.boolean strictMethodInvocation:标示是否启动SMI.关于SMI请找相关的知识点。
好了。理解了PackageConfig类的信息之后,让我们看一下addPackage方法代码吧。
protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
String packageName = packageElement.getAttribute("name");
PackageConfig packageConfig = configuration.getPackageConfig(packageName);
if (packageConfig != null) {
LOG.debug("Package [{}] already loaded, skipping re-loading it and using existing PackageConfig [{}]", packageName, packageConfig);
return packageConfig;
} PackageConfig.Builder newPackage = buildPackageContext(packageElement); if (newPackage.isNeedsRefresh()) {
return newPackage.build();
} LOG.debug("Loaded {}", newPackage); // 增加结果类型到newPackage里面去。
addResultTypes(newPackage, packageElement); // 增加拦截器和拦截栈到newPackage里面去。
loadInterceptors(newPackage, packageElement); // 设置newPackage的默认拦截器
loadDefaultInterceptorRef(newPackage, packageElement); // 设置newPackage的默认类,即是action类的父类
loadDefaultClassRef(newPackage, packageElement); // 增加公共结果到newPackage里面去。
loadGlobalResults(newPackage, packageElement);
//设置允许的方法
loadGlobalAllowedMethods(newPackage, packageElement); // 增加异常处理newPackage里面去。
loadGlobalExceptionMappings(newPackage, packageElement); // 加载对应的action信息。并增加到newPackage里面去。
NodeList actionList = packageElement.getElementsByTagName("action"); for (int i = 0; i < actionList.getLength(); i++) {
Element actionElement = (Element) actionList.item(i);
addAction(actionElement, newPackage);
} // 设置newPackage默认的ACTION
loadDefaultActionRef(newPackage, packageElement); PackageConfig cfg = newPackage.build();
configuration.addPackageConfig(cfg.getName(), cfg);//增加到配置类里面
return cfg;
}
从上面的代码中我们可以发现最后获得package元素信息都会增加Configuration接口对应的实例。即是DefaultConfiguration类的实例。这个方法也面也调用了很多方法来完成增加package元素。这些方法笔者并不想讲解。请读者自行根据笔者对方法的定义去查看源码。而这里面有一点到是值得笔者注意的。那便是在PackageConfig类的实例的时候,好像用到建造者模式来实现。所以读者在查看源码的时候,如果不懂为什么作者要这样子写的话。请自行去查看相关的建造者模式的知识点。而加载package元素信息的工作到这里就算是结束了。
在加载package元素信息的工作结束之后,还有一件工作也是值得注意的。那便是上面reloadContainer方法代码中出现的rebuildRuntimeConfiguration方法。这个方法做了什么呢?在笔者理解为创建一个运行时的配置信息,用于方便调用。在什么时候调用呢?至少笔者在DefaultActionProxy类的prepare方法调用到了。这个prepare方法是在action请求执行action将用到。详细的内容笔者会在后面的章节里面讲到。rebuildRuntimeConfiguration方法最后会创建一个叫RuntimeConfiguration接口的实例,即是RuntimeConfigurationImpl类的实例。
本章总结 |
本章的重点是知道struts2是如何加载相关的package元素节点信息的。那为什么要知道这部分的内容。相信笔者心里面应该笔者更清楚。如果不知道package元素的信息。那么struts2如何根据用户输入的URL来处理和运行相关的action类呢?不知道笔者是否还记得核心机制的图片。可以这么说吧。到这一章相关橙黄色(Servlet Filters)部分的知识可以都结束了。我们知道如何加载相关的配置信息,知道如何加载package元素信息。而下一章笔者将对蓝色(Struts core)部分的知识进行讲解。即是根据现有的配置信息来处理用户发来的action请求。
Struts2 源码分析——配置管理之PackageProvider接口的更多相关文章
- Struts2 源码分析——配置管理之ContainerProvider接口
本章简言 上一章笔者讲到关于Dispatcher类的执行action功能,知道了关于执行action需要用到的信息.而本章将会讲到的内容也跟Dispatcher类有关系.那就是配置管理中的Contai ...
- Struts2 源码分析——拦截器的机制
本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
- Struts2 源码分析——过滤器(Filter)
章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...
- Struts2 源码分析——DefaultActionInvocation类的执行action
本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...
- Struts2 源码分析——调结者(Dispatcher)之执行action
章节简言 上一章笔者写关于Dispatcher类如何处理接受来的request请求.当然读者们也知道他并非正真的执行action操作.他只是在执行action操作之前的准备工作.那么谁才是正真的执行a ...
- Struts2 源码分析——Hello world
新建第一个应用程序 上一章我们讲到了关于struts2核心机制.对于程序员来讲比较概念的一章.而本章笔者将会亲手写一个Hello world的例子.所以如果对struts2使用比较了解的朋友,请跳过本 ...
- Struts2 源码分析-----工作原理分析
请求过程 struts2 架构图如下图所示: 依照上图,我们可以看出一个请求在struts的处理大概有如下步骤: 1.客户端初始化一个指向Servlet容器(例如Tomcat)的请求: 2.这个请求经 ...
- Struts2 源码分析——Result类实例
本章简言 上一章笔者讲到关于DefaultActionInvocation类执行action的相关知识.我们清楚的知道在执行action类实例之后会相关处理返回的结果.而这章笔者将对处理结果相关的内容 ...
随机推荐
- Unity依赖注入使用详解
写在前面 构造器注入 Dependency属性注入 InjectionMethod方法注入 非泛型注入 标识键 ContainerControlledLifetimeManager单例 Unity注册 ...
- ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期
开篇:上一篇我们了解了所谓的请求处理管道,在众多的事件中微软开放了19个重要的事件给我们,我们可以注入一些自定义的业务逻辑实现应用的个性化设计.本篇,我们来看看WebForm模式下的页面生命周期. ( ...
- (源码下载)高灵活度,高适用性,高性能,轻量级的 ORM 实现
我在上一篇博客中简单说明了一个面向内存数据集的“ORM”的实现方法,也提到我的设计实现或许不能称之为“ORM”,姑且称之为 S-ORM吧. 可能有些小伙伴没有理解我的思路和目的,与传统ORM框架做了简 ...
- A*寻路算法
对于初学者而言,A*寻路已经是个比较复杂的算法了,为了便于理解,本文降低了A*算法的难度,规定只能横竖(四方向)寻路,而无法直接走对角线,使得整个算法更好理解. 简而言之,A*寻路就是计算从起点经过该 ...
- .NET中XML序列化的总结
[题外话] 以前虽然常用.NET中的序列化,但是常用的BinaryFormatter,也就是二进制文件的序列化,却鲜用XML的序列化.对于XML序列化,.NET中同样提供了一个非常方便的工具XmlSe ...
- [异常解决] ubuntu上安装JLink驱动遇到的坑及给后来者的建议
一.前言 最近将整个电脑格式化,改成了linux操作系统 希望这样能让自己在一个新的世界探索技术.提升自己吧- win上的工具用多了,就不想变化了- 继上一篇<ubuntu上安装虚拟机遇到的问题 ...
- dofile执行ANDROID APK里面的文件
我使用dofile执行APK文件是不行的,比如 dofile("assets/res/flist")只能先拷贝到writablePath然后再dofile拿到数据后再清除这个临时文 ...
- C# 用原生JS进行文件的上传
1.此文章是用原生JS来进行文件的上传,有两个版本,一个不用ajax,一个用ajax. 1)非AJAX <!DOCTYPE html> <html> <head> ...
- Bootstrap~学习笔记索引
回到占占推荐博客索引 bootstrap已经用了有段时间了,感觉在使用上还是比较容易接受的,在开发人员用起来上,也还好,不用考虑它的兼容性,手机,平台,PC都可以有效的兼容. bootstrap官方a ...
- Atitit 教育与培训学校 的计划策划 v4 qc18
Atitit 教育与培训学校 的计划策划 v4 qc18 1.1. 版本历史12. 教育历史的前世今生12.1. 自学vs 家庭学校vs 私立学校vs 公立学校模式 vs 企业内部学校 vs 其他商业 ...