本章简言

上一章笔者讲到关于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接口的更多相关文章

  1. Struts2 源码分析——配置管理之PackageProvider接口

    本章简言 上一章讲到关于ContainerProvider的知识.让我们知道struts2是如何注册相关的数据.也知道如何加载相关的配置信息.本章笔者将讲到如何加载配置文件里面的package元素节点 ...

  2. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  3. Struts2 源码分析——Action代理类的工作

    章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...

  4. Struts2 源码分析——过滤器(Filter)

    章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...

  5. Struts2 源码分析——DefaultActionInvocation类的执行action

    本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...

  6. Struts2 源码分析——调结者(Dispatcher)之执行action

    章节简言 上一章笔者写关于Dispatcher类如何处理接受来的request请求.当然读者们也知道他并非正真的执行action操作.他只是在执行action操作之前的准备工作.那么谁才是正真的执行a ...

  7. Struts2 源码分析——Hello world

    新建第一个应用程序 上一章我们讲到了关于struts2核心机制.对于程序员来讲比较概念的一章.而本章笔者将会亲手写一个Hello world的例子.所以如果对struts2使用比较了解的朋友,请跳过本 ...

  8. Struts2 源码分析-----工作原理分析

    请求过程 struts2 架构图如下图所示: 依照上图,我们可以看出一个请求在struts的处理大概有如下步骤: 1.客户端初始化一个指向Servlet容器(例如Tomcat)的请求: 2.这个请求经 ...

  9. Struts2 源码分析——Result类实例

    本章简言 上一章笔者讲到关于DefaultActionInvocation类执行action的相关知识.我们清楚的知道在执行action类实例之后会相关处理返回的结果.而这章笔者将对处理结果相关的内容 ...

随机推荐

  1. 编译安装PHP的参数 --with-mysql --with-mysqli --with-apxs2默认路径

    编译安装PHP时指定如下几个参数说明: --with-apxs2=/usr/local/apache/bin/apxs //整合apache,apxs功能是使用mod_so中的LoadModule指令 ...

  2. IEEE/ACM ASONAM 2014 Industry Track Call for Papers

    IEEE/ACM International Conference on Advances in Social Network Analysis and Mining (ASONAM) 2014 In ...

  3. ABP理论学习之依赖注入

    返回总目录 本篇目录 什么是依赖注入 传统方式产生的问题 解决办法 依赖注入框架 ABP中的依赖注入基础设施 注册 解析 其他 ASP.NET MVC和ASP.NET Web API集成 最后提示 什 ...

  4. ABP理论学习之数据传输对象(DTO)

    返回总目录 本篇目录 为何需要DTO 领域层抽象 数据隐藏 序列化和懒加载问题 DTO惯例和验证 DTO和实体的自动映射 使用特性和扩展方法进行映射 帮助接口 DTO用于应用层和 展现层间的数据传输. ...

  5. ABP理论学习之异常处理

    返回总目录 本篇目录 介绍 开启错误处理 非Ajax请求 展示异常信息 UserFriendlyException Error模型 Ajax请求 异常事件 介绍 在一个web应用中,异常通常是在MVC ...

  6. 让浏览器不再显示 https 页面中的 http 请求警报

    HTTPS 是 HTTP over Secure Socket Layer,以安全为目标的 HTTP 通道,所以在 HTTPS 承载的页面上不允许出现 http 请求,一旦出现就是提示或报错: Mix ...

  7. java中文乱码解决之道(四)-----java编码转换过程

    前面三篇博客侧重介绍字符.编码问题,通过这三篇博客各位博友对各种字符编码有了一个初步的了解,要了解java的中文问题这是必须要了解的.但是了解这些仅仅只是一个开始,以下博客将侧重介绍java乱码是如何 ...

  8. Javascript中相同Function使用多个名称

    原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com   看Log4js源码有如下实现: ['Trace','Debug','Info','Warn','Error', ...

  9. 如何调试ANDROID下面黑屏问题

    最近很多朋友在问,为毛在WINDOWS下对了,跑ANDROID的虚拟机或者真机就黑屏了, 有的是只有FPS信息,有的是连FPS信息都没有.如果是程序能够正常启动,不会闪退,但显示不对. 那十有八九都是 ...

  10. TroubleShooting笔记--快照进程sp_replupdateschema和索引重建发生冲突

    今天早上服务器出现大面积的阻塞,上去排查blocking,最后大概确定的问题是: rebuild index job(243) --->blocked--->sp_replupdatesc ...