Struts2 源码分析——配置管理之ContainerProvider接口
本章简言 |
上一章笔者讲到关于Dispatcher类的执行action功能,知道了关于执行action需要用到的信息。而本章将会讲到的内容也跟Dispatcher类有关系。那就是配置管理中的ContainerProvider类。我们都知道在struts2启动的时候,struts2会去加载对应的配置文件。如struts.xml文件等。如果笔者没有记错的话。在《Struts2 源码分析——调结者(Dispatcher)之准备工作》的章节里面讲到过关于ConfigurationManager类的一些知识。可是并没有深入的研究和讲解。先让我们回顾一下吧。ConfigurationManager类里面会存放大量供应者的类。供应者就是专门为Container容器提供数据的中间者。而ContainerProvider接口又是所有供应者的父接口,这也笔者不得不讲解的理由了。
Container容器 |
在讲解到ContainerProvider接口的时候,笔者要先讲一下Container容器相关的知识。主要是Container容器和供应者的关系太微妙了。但是笔者不想讲解关于Container容器的源码实现。这并不是笔者写这一系列的课题。只是跟这个课题有关系而以。笔者不清楚读者有没有用Spring框架。Spring框架便是JAVA界里面实现IOC框架中最好的框架之一。笔者看过很多IOC的框架。如.NET界的Autofac。所以笔者知道一点:一般实现IOC思想的框架都会有俩个类。即是Container类和ContainerBuilder类。
ContainerBuilder类是用于创建Container容器的。Container容器里面会存放大量的类的实例和类的信息。而Container容器又是什么知道自己本身里面存放了这些类的实例和类的信息。总要有人跟Container容器讲吧。所以ContainerBuilder类的工作就是注册相关将来要在Container容器里面存放的类实例和类的信息。注册的代码体现在不同的作者有着不同的表现。这边表现在factory方法。当然除了factory方法用于注册相关类的信息。还有其他的三个方法。如下
1.factory方法:注册类的信息。
2.alias方法:注册一个别名。即是本身存在一个类的信息,现在想让他拥有一个别名,通过这个别名也能获得这个类的信息。
3.constant方法:注册一个常理。Map类的用法大家都知道吧。这个用法也是一个的。给一个KEY就可以获得对应的VALUE。
4.create方法:用于创建Container容器。
上面的四个方法就是当前struts2最常用到。同时还要注意的是每一个类的信息都会有自己的生命周期。可是一个线程一个实例。也能是每次获得都是最新的实例。也可是从头到尾都是用一个实例。关键字Scope就是最好的代码体现。Scope是一个enum类型。他就是存放了关于类信息的生命周期。有几种的读者请自己查看吧。
Container类是用于存放类的信息和类的实例。他的作用容器一样子。那如何获得呢?这里就要讲到关于IOC思想里面有一个概念。即是依赖注入。也就是inject方法。那读者就会说有没有像用MAP类那样子的用法呢?笔者可以明确的跟你讲:有。
1.inject方法:用于依赖注入。什么是依赖注入呢?简单的讲就是你用了Container类实例的inject方法来作用一个类实例的话。当类实例所有需要注入的属性都会帮你生成。不用你自己动手去实例。在代码中注解@Inject就是最好的体现。如下
Dispatcher类的init方法:
Container container = init_PreloadConfiguration();// 初始化相关的配置信息,并加载以上供应者。
container.inject(this);// 注入当前类依赖项的信息。
2.getInstance方法:不用笔者讲。大家一看就明白。就是传入一个KEY就可以获得对应的实例了。
ContainerProvider接口 |
有了上面小节的知识,笔者在来讲解ContainerProvider接口的时候,很多问题就会变得很简单。Dispatcher类里面有一个成员变量configurationManager(ConfigurationManager类)。configurationManager变量在初始化Container容器的时候(在init_PreloadConfiguration方法里面)开始起作用了。在此之前configurationManager变量只是增加相关的供应者而以。为什么在初始化Container容器的时候要用到ConfigurationManager类的实例呢?那就要讲到一个接口Configuration。Configuration接口默认有一个实现类DefaultConfiguration类。可以说Container容器的初始化必须通过Configuration接口。看一下代码吧。
Dispatcher类:
/**
* @return 获得对应的容器
*/
public Container getContainer() {
if (ContainerHolder.get() != null) {
return ContainerHolder.get();
}
ConfigurationManager mgr = getConfigurationManager();
if (mgr == null) {
throw new IllegalStateException("The configuration manager shouldn't be null");
} else {
Configuration config = mgr.getConfiguration();
if (config == null) {
throw new IllegalStateException("Unable to load configuration");
} else {
Container container = config.getContainer();
ContainerHolder.store(container);
return container;
}
}
}
从上面的代码笔者就能明白。获得ConfigurationManager类实例,在通过ConfigurationManager类实例来获得对应的Configuration接口实例。即是DefaultConfiguration类实例。Configuration接口里面有一个getContainer方法来获得Container容器。关键就是要停留在ConfigurationManager类的getConfiguration方法上面。为什么呢?看一下代码吧。
ConfigurationManager类:
public synchronized Configuration getConfiguration() {
if (configuration == null) {
setConfiguration(createConfiguration(defaultFrameworkBeanName));
try {
configuration.reloadContainer(getContainerProviders());//加载初始化Container容器
} catch (ConfigurationException e) {
setConfiguration(null);
throw new ConfigurationException("Unable to load configuration.", e);
}
} else {
conditionalReload();//判断是否重新加载初始化Container容器
} return configuration;
}
看样子笔者不必多讲就能看出实际上加载初始化Container容器在获得Configuration接口实例的时候就已经初始化完成了。这也是本章核心代码部分。即是Configuration接口的reloadContainer方法。只要明白了这方法的工作就可以知道很多事件。代码如下
DefaultConfiguration类:
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
List<PackageProvider> packageProviders = new ArrayList<>();//用于存放用于实现PackageProvider接口的供应者类 ContainerProperties props = new ContainerProperties();//用于常量存放,当然这边加工了Properties类。多出一个叫本地化的值。即是KEY-VALUE-LOCATION
ContainerBuilder builder = new ContainerBuilder();//用于创建Container容器的类
Container bootstrap = createBootstrapContainer(providers);//新建一个Container容器用于初始化信息。
for (final ContainerProvider containerProvider : providers)
{
bootstrap.inject(containerProvider);
containerProvider.init(this);//初始化对应的供应者。
containerProvider.register(builder, props);//注册供应者的数据
}
props.setConstants(builder);//把常量注册到builder里面去 //注册Configuration
builder.factory(Configuration.class, new Factory<Configuration>() {
public Configuration create(Context context) throws Exception {
return DefaultConfiguration.this;
}
}); /*****************************************************分界线*****************************************************************/
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);
}
}
return packageProviders;
}
应该说这个方法可以分为上部分和下部分来讲。上部分是为用于初始化并注册供应者提供的数据。而下部分是为加载对应有package元素的供应者的数据。如strust.xml的package节点。其实这个方法里面总共新建了俩次Container容器。第一个容器是为了初始化时候用到的。而第二个容器才是最终要用的容器。containerProvider.register(builder, props)这句代码就是可以明白了《Struts2 源码分析——调结者(Dispatcher)之准备工作》的章节里面讲到的ContainerProvider接口。也是containerProvider接口和Dispatcher类这间的关系体现。
上部分中第一次初始化了一个Container容器,调用createBootstrapContainer方法来实现Container容器。createBootstrapContainer方法里注册了一些基本要用到的类信息。这里就不细详的讲说。同时我们还发现一个类ContainerProperties,所有常量注册都是先存放在这个类里面,然后在注册到ContainerBuilder类里面。最后在把DefaultConfiguration本身注册进去。关键的代码部分笔者已经在上面代码中标红色了。这个部分就是触发containerProvider接口的注册。
下部分中又在一次初始化Container容器用于加载有package元素节点的配置文件的包供应者。包供应者都是继承了PackageProvider接口。PackageProvider接口(笔者会在下一个章节讲到)
最后我们会看到一个rebuildRuntimeConfiguration方法。这个方法的功能就是创建一个运行时的配置。用于运行时候调用。
看到这里的时候。笔者就是明白了。如果想要知道什么加载配置文件就必须去找到对应的供应者类。那么对应加载配置文件的供应者类有StrutsXmlConfigurationProvider和父类XmlConfigurationProvider。只要了解了这俩个类相信笔者就明白了什么加载了。主要是要看XmlConfigurationProvider类的register方法。代码如下。
XmlConfigurationProvider类:
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
LOG.trace("Parsing configuration file [{}]", configFileName);
Map<String, Node> loadedBeans = new HashMap<>();
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 ("bean".equals(nodeName)) {//加载Bean节点
String type = child.getAttribute("type");
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
String onlyStatic = child.getAttribute("static");
String scopeStr = child.getAttribute("scope");
boolean optional = "true".equals(child.getAttribute("optional"));
Scope scope = Scope.SINGLETON;
if ("prototype".equals(scopeStr)) {
scope = Scope.PROTOTYPE;
} else if ("request".equals(scopeStr)) {
scope = Scope.REQUEST;
} else if ("session".equals(scopeStr)) {
scope = Scope.SESSION;
} else if ("singleton".equals(scopeStr)) {
scope = Scope.SINGLETON;
} else if ("thread".equals(scopeStr)) {
scope = Scope.THREAD;
} if (StringUtils.isEmpty(name)) {
name = Container.DEFAULT_NAME;
} try {
Class classImpl = ClassLoaderUtil.loadClass(impl, getClass());
Class classType = classImpl;
if (StringUtils.isNotEmpty(type)) {
classType = ClassLoaderUtil.loadClass(type, getClass());
}
if ("true".equals(onlyStatic)) {
// Force loading of class to detect no class def found exceptions
classImpl.getDeclaredClasses();
containerBuilder.injectStatics(classImpl);
} else {
if (containerBuilder.contains(classType, name)) {
Location loc = LocationUtils.getLocation(loadedBeans.get(classType.getName() + name));
if (throwExceptionOnDuplicateBeans) {
throw new ConfigurationException("Bean type " + classType + " with the name " +
name + " has already been loaded by " + loc, child);
}
} // Force loading of class to detect no class def found exceptions
classImpl.getDeclaredConstructors(); LOG.debug("Loaded type: {} name: {} impl: {}", type, name, impl);
containerBuilder.factory(classType, name, new LocatableFactory(name, classType, classImpl, scope, childNode), scope);
}
loadedBeans.put(classType.getName() + name, child);
} catch (Throwable ex) {
if (!optional) {
throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
} else {
LOG.debug("Unable to load optional class: {}", impl);
}
}
} else if ("constant".equals(nodeName)) {//加载常量节点
String name = child.getAttribute("name");
String value = child.getAttribute("value");
props.setProperty(name, value, childNode);
} else if (nodeName.equals("unknown-handler-stack")) {
List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
int unknownHandlersSize = unknownHandlers.getLength(); for (int k = 0; k < unknownHandlersSize; k++) {
Element unknownHandler = (Element) unknownHandlers.item(k);
Location location = LocationUtils.getLocation(unknownHandler);
unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location));
} if (!unknownHandlerStack.isEmpty())
configuration.setUnknownHandlerStack(unknownHandlerStack);
}
}
}
}
}
相关这一段代码笔者并没有注解相关的内容。主要是代码里面的一些方法在文章中都有讲到。这个部分主要是读取XML的内容。然后注册到ContainerBuilder类里面。 看完这一段代码之后,笔者相信朋友们心里面都有一些明白了。
本章总结 |
本章主要是讲到了关于ContainerProvider接口的知识点。ContainerProvider接口是所有供应者的父接口。用于注册类信息提供了帮助。值得注意的是要明白Dispatcher类和ConfigurationManager类的关系,ConfigurationManager类和Configuration接口的关系,Configuration接口又跟Container类的关系,Container类又跟ContainerProvider接口的关系。
Struts2 源码分析——配置管理之ContainerProvider接口的更多相关文章
- Struts2 源码分析——配置管理之PackageProvider接口
本章简言 上一章讲到关于ContainerProvider的知识.让我们知道struts2是如何注册相关的数据.也知道如何加载相关的配置信息.本章笔者将讲到如何加载配置文件里面的package元素节点 ...
- 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类实例之后会相关处理返回的结果.而这章笔者将对处理结果相关的内容 ...
随机推荐
- 眼见为实:.NET类库中的DateTimeOffset用途何在
在 EnyimMemcachedCore(支持.NET Core的memached客户端)中实现 Microsoft.Extensions.Caching.Distributed.IDistribut ...
- ENode框架Conference案例分析系列之 - 上下文划分和领域建模
前面一片文章,我介绍了Conference案例的核心业务,为了方便后面的分析,我这里再列一下: 业务描述 Conference是这样一个系统,它提供了一个在线创建会议以及预订会议座位的平台.这个系统的 ...
- 编写具有单一职责(SRP)的类
这两周我需要对一个历史遗留的功能做一些扩展,正如很多人不愿意碰这些历史遗留的代码一样,我的内心也同样对这样的任务充满反抗.这些代码中充斥着各种null判断(你写的return null正确吗?),不规 ...
- MySQL 对比数据库表结构
200 ? "200px" : this.width)!important;} --> 介绍 本章主要介绍怎样对比数据库的表结构的差异,这里主要介绍使用mysqldiff工具 ...
- MySQL RANGE分区
200 ? "200px" : this.width)!important;} --> 介绍 RANGE分区基于一个给定的连续区间范围,早期版本RANGE主要是基于整数的分区 ...
- 干掉Unity3D
我为什么想干掉Unity3D? 这个问题容我不答,每个做技术的人总有一些完美主义. 你使用u3d的过程中是不是痛并快乐着呢. 就从两个国内具有相当普遍性的痛点说起. il2cpp,unity作出了这个 ...
- Btree 索引
Btree 索引 索引是帮助数据库高效获取数据的一种数据结构,通过提取句子主干,就可以得到索引的本质. m-way查找树 如果想了解Btree,需要首先了解m-way数据结构. m-way查找树是是一 ...
- [nRF51822] 13、浅谈nRF51822和NRF24LE1/NRF24LU1/NRF24L01经典2.4G模块无线通信配置与流程
前言: nRF51可以支持基于2.4G的互相通信.与NRF24LE1的通信.与NRF24LU1的通信.与NRF24L01的通信. 一.nRF51822基于2.4G和nRF51822通信 其中nRF5 ...
- Tomcat 让百度的域名显示自己的页面内容(玩耍篇)
步骤一:在 C:\Windows\System32\drivers\etc目录下的Hosts文件中添多一个条目,如下图, 第二步:在server.xml文件中做响应配置,如下图 然后在Host节点中进 ...
- Step by step 活动目录中添加一个子域
原创地址:http://www.cnblogs.com/jfzhu/p/4006545.html 转载请注明出处 前面介绍过如何创建一个域,下面再介绍一下如何在该父域中添加一个子域. 活动目录中的森林 ...