Webx是一套基于Java Servlet API的通用Web框架。Webx致力于提供一套极具扩展性的机制,来满足Web应用不断变化和发展的需求。而SpringExt正是这种扩展性的基石。SpringExt扩展了Spring,在Spring的基础上提供了一种扩展功能的新方法。
Springext概念
springext是基于spring的IOC机制扩展,springext提供了众多自己的工具箱,不仅于此,它最大的特点是提供给开发人员一种扩展spring bean管理的设计方案。springext涉及到了两个概念:扩展点和捐献。
- 扩展点ConfigurationPoint
- 扩展点相当于接口,和java接口的差别在于,扩展点和一个namespace相关联
- 捐献Contribution
- 捐献相当于实现,同时捐献和相应扩展点的namespace下的一个element相关联。
扩展点的定义默认放在spring.configuration-points,初始化过程中会默认加载该文件,寻找扩展点与命名空间的对应情况。然后从*.bean-definition-parsers寻找对应的捐献解析类。
流程:
开发者1定义扩展点,也可以创建捐献。
开发者2可以捐献给这个扩展点。
开发者1不需要和开发者2沟通,即可使用开发者2所提供的实现,以及xml语法。举例说明:
是webx中的一个重要服务。它里面定义了一个扩展点,叫作template-engines。
假定我是上面所说的开发者1,开发了template-service。而我并不知道velocity相关的任何知识。
假定开发者2想为系统添加一个velocity模板的支持,它只需要捐献给这个扩展点。
类似的,开发者3开发了jsp模板;开发者4开发了freemarker模板。
而使用template-service的人,只需要把包含了velocity/jsp/freemarker捐献的jar包配置在maven依赖中,就可以使用它们了。
可以这样配置:
1
2
3
4
5
6
7
8
9 <services:template xmlns="http://www.alibaba.com/schema/services/template/engines" searchExtensions="true">
<velocity-engine templateEncoding="UTF-8" strictReference="false" path="/${component}/templates">
<global-macros>
<name>global/xx.vm</name>
</global-macros>
</velocity-engine>
<freemarker-engine templateEncoding="UTF-8" path="/${component}/templates" />
<jsp-engine path="/${component}/templates" />
</services:template>
进一步的,捐献者还可以创建自己的扩展点。例如velocity-engine创建了一个扩展点,让其它人可以扩展velocity的功能:1
2
3
4
5
6
7
8
9
10
11
12<velocity-engine templateEncoding="UTF-8" strictReference="false" path="/${component}/templates">
<plugins>
<vm-plugins:escape-support defaultEscape="html">
<vm-plugins:noescape>
<vm-plugins:if-matches pattern="^control\." />
<vm-plugins:if-matches pattern="^screen_placeholder" />
<vm-plugins:if-matches pattern="^stringEscapeUtil\.escape" />
<vm-plugins:if-matches pattern="^csrfToken\.(get)?(\w*)hiddenField" />
</vm-plugins:noescape>
</vm-plugins:escape-support>
</plugins>
</velocity-engine>
–来自baobao
Spring IOC容器
Spring的IOC容器是整个Springext的基础。要解释SpringExt就必须要了解Spring的IOC容器的基本原理和概念。
BeanDefinition
IOC是一个容器,容器里面装的东西就是BeanDefinition。
- 把IOC比作水桶,那么BeanDefinition就是水。是容器的重要组成部分
- BeanDefinition是对实体Bean的抽象数据结构表述,并不是真正实例化的Bean。BeanDefinition的部分定义如下图。
BeanFactory与ApplicationContext
以BeanFactory和ApplicationContext为中心的继承关系图如下:
- BeanFactory:BeanFactory定义了基本IOC容器的规范,包括了基本的getBean()类似的方法。该接口的其他衍生接口这里由于时间问题,笔者也没有仔细看,就不多解释了。
- ApplicationContext:可以从继承结构中看到,该接口继承自BeanFactory,所以当然具备了获取Bean等基本功能,只不过这里细化了一些功能,同时也丰富了BeanFactory。如果说BeanFactory是一个容器,那么它也只是个简单的容器,ApplicationContext丰富了很多高级特性。
SpringExt在Spring IOC容器的初始化分析
Spring IOC的初始化过程可以分为三个过程
- 资源定位
- 载入BeanDefinition
- 向IOC注册BeanDefinition
这里我们因为是为了解释SpringExt,所以只关注资源定位,以及BeanDefinition的载入。上篇文章说到,在WebxContextLoaderListener创建了web应用的上下文,而创建上下文调用了方法createWebApplicationContext(),该方法的片段如下:1
2
3
4
5
6
7
8 ...
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setParent(parent);
wac.setServletContext(servletContext);
wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM));
customizeContext(servletContext, wac);
wac.refresh();
...
忽略其他的代码,我们这里只看refresh()方法,refresh方法代码片段如下:1
2
3
4
5
6
7
8
9 prepareRefresh();//刷新准备,如果已存在beanfactory则销毁置null。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//重新获取BeanFactory,做一定的初始化并作一定的定制。通过xml解析器解析得到BeanDefinition(将要放到IOC容器的数据结构)
prepareBeanFactory(beanFactory);//设置类加载器以及类依赖
try {
...
finishBeanFactoryInitialization(beanFactory);//查看是否有需要提前实例化的类,有则在此阶段实例化
...
finishRefresh();
}
与资源加载相关的比较重要的是obtainFreshBeanFactory()方法。该方法中判断是否存在BeanFactory,如果存在则销毁重新创建,然后为了重新加载BeanDefinitions调用了spring.framework.XmlWebApplicationContext的loadBeanDefinitions()方法。该方法的定义如下:1
2
3
4
5
6
7
8
9
10// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);//增加扩展点的支持
loadBeanDefinitions(beanDefinitionReader);//重载的接口,来自webx的XmlWebApplicationContext。在这里获取所有的默认的配置文件(webx.xml和webx-*.xml)
此处高能反应,刨了那么久终于找到了springext的痕迹。这里我们重点看两个方法initBeanDefinitionReader(初始化BeanDefinition解析器)和本方法中的loadBeanDefinitions(注意此方法是Spring为了支持框架扩展而提供的重载方法)。
initBeanDefinitionReader
该方法只有一行就是调用addConfigurationPointsSupport(),字面解释直译是添加扩展点支持。看到这个我差点没忍住啊,瞬间GC了。该方法的代码片段如下:1
2
3
4
5
6
7
8// schema providers
SpringExtSchemaSet schemas = new SpringExtSchemaSet(classLoader);//DEFAULT_CONFIGURATION_POINTS_LOCATION="META-INF/spring.configuration-points"
// default resolvers
EntityResolver defaultEntityResolver = new ResourceEntityResolver(resourceLoader);
NamespaceHandlerResolver defaultNamespaceHandlerResolver = new DefaultNamespaceHandlerResolver(classLoader);
// 以下封装了spring作为默认的类型解析器和命名空间解析器,调用顺序为,先用webx的解析器来解析类型,如果不通过再转交给spring
EntityResolver entityResolver = new SchemaEntityResolver(defaultEntityResolver, schemas);
NamespaceHandlerResolver namespaceHandlerResolver = new ConfigurationPointNamespaceHandlerResolver(schemas.getConfigurationPoints(), defaultNamespaceHandlerResolver);
完全靠猜也知道这是在干什么啊。首先获取SpringExtSchemaSet,该类封装了springext的各种Schema文件和configuration-points文件信息。然后定义了两个类EntityResolver和NamespaceHandlerResolver,这两个类是为了解决类型解析和命名空间解析的问题。SpringExt扩展了这两个类,用来添加解析自己定义的元素,两个类分别为:SchemaEntityResolver和ConfigurationPointNamespaceHandlerResolver,
- SchemaEntityResolver:该类功能为了解决通过url来定位xsd的位置的问题。由于SchemaEntityResolver持有一个spring默认的defaultEntityResolver,所以如果找不到,再交给spring的默认类即可。
- ConfigurationPointNamespaceHandlerResolver:根据命名空间,找到各个xml元素节点对应的parser放到hashmap中,而key对应为该扩展点的节点名。
loadBeanDefinitions
initBeanDefinitionReader方法的作用很明显,从名字直译就是初始化BeanDefinition阅读器,意思就是BeanDefinition载入器。该方法主要为了通过准备Schema以及设定xml元素对应的解析类来完成reader的初始化。接下来,当然是从xml解析BeanDefinition了。loadBeanDefinitions方法源码如下:1
2
3
4
5
6 String[] configLocations = getConfigLocations();//获取配置文件路径
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);//依次加载BeanDefinition
}
}
这里方法内部的loadBeanDefinitions()是XmlBeanDefinitionReader类的方法,注意区分。一共有三个,一个是从spring框架xmlwebapplicationcontext继承过来的方法,一个是webx的xmlwebapplicationcontext的重载方法,这里的这个则是XmlBeanDefinitionReader类的方法.
这个方法的逻辑就是获取配置文件信息:加载规则是读取webx.xml和webx-*.xml:1
2
3
4
5
6
7
8
9
10@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] { WEBX_COMPONENT_CONFIGURATION_LOCATION_PATTERN.replace("*", getNamespace()) };
} else {
return new String[] { WEBX_CONFIGURATION_LOCATION };
}
}
//WEBX_CONFIGURATION_LOCATION = "/WEB-INF/webx.xml"
//WEBX_COMPONENT_CONFIGURATION_LOCATION_PATTERN = "/WEB-INF/webx-*.xml"
到这里了,感觉一身爽。啧啧~
爽完就总结下
梳理完整了整个流程,再简单的描述下。首先,接上篇文章的流程来,当CreateWebApplicationContext的时候,ConfigurableWebApplicationContext会refresh自身,导致整个框架触发解析资源的操作。也就是说refresh()方法是导致整个资源加载的导火线。由于springext扩展了spring的xml解析方法,springext拿到所有需要解析的资源文件,先去解析自己能力范围之内的东西,其他的转交给spring框架自身。解析完BeanDefinition之后,这些资源被加入到IOC容器之中等待被第一次使用的时候被实例化。不过,例外的是,如果bean类型被设置为预加载则会在此时做实例化操作。
相关资料
[1]http://www.openwebx.org/forum/showthread.php?tid=352
[2]http://www.blogjava.net/DLevin/archive/2012/11/18/391545.html