大致了解SpringExt
Spring Schema提供了我们便捷的初始化bean的方法,我们不需要再去写构造器注入或者属性注入直接使用类似的如下配置代码即可完成bean的初始化
<resource-loading id="resourceLoadingService"
xmlns="http://www.alibaba.com/schema/services/resource-loading">
<resource pattern="/file">
<file-loader basedir="${user.home}" />
</resource>
<resource pattern="/webroot">
<webapp-loader />
</resource>
</resource-loading>
但是Spring Schema是不具有拓展性的,我们不能随意添加一个database-loader
,对比以上代码
<resource-loading id="resourceLoadingService"
xmlns="http://www.alibaba.com/schema/services/resource-loading">
<resource pattern="/file">
<file-loader basedir="${user.home}" />
</resource>
<resource pattern="/webroot">
<webapp-loader />
</resource>
<resource pattern="/db">
<database-loader connection="jdbc:mysql:mydb" />
</resource>
</resource-loading>
于是SpringExt帮助我们通过定义拓展点(ConfigurationPoint)
和声明贡献(Contribution)
的方式实现我们的动态拓展功能:
<resource-loading id="resourceLoadingService"
xmlns="http://www.alibaba.com/schema/services"
xmlns:loaders="http://www.alibaba.com/schema/services/resource-loading/loaders">
<resource pattern="/file">
<loaders:file-loader basedir="${user.home}" />
</resource>
<resource pattern="/webroot">
<loaders:webapp-loader />
</resource>
<resource pattern="/db">
<loaders:database-loader connection="jdbc:mysql:mydb" />
</resource>
</resource-loading>
同时SpringExt提供将远程的xsd文件存储至本地的功能
以上的例子请详细阅读Webx的文档:http://www.openwebx.org/docs/Webx3_Guide_Book.html#webx.overview.springext
Spring IOC 初始化
spring在DefaultBeanDefinitionDocumentReader.parseBeanDefinitions方法中会根据当前的element的namespaceUri判断是否是Spring定义的还是用户自定义的
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
SpringExt就是借助自定义区进行解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
实现自定义的基本流程
1.编写xsd文件
截取了一段
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/tool"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/tool"
elementFormDefault="qualified">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
...
<xsd:complexType name="registersScopeType">
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the name of a custom bean scope that the annotated type registers, e.g. "conversation".
Such a scope will be available in addition to the standard "singleton" and "prototype" scopes
(plus "request", "session" and "globalSession" in a web application environment).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>
2.META-INF/spring.schemas 文件中建立namespace.xsd 与xsd 文件实际路径的映射
也就是类似于
http://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
3.编写NamespaceHandler,并在META-INF/spring.handlers建立Namespace与NamespaceHandler的联系
http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
4.实现若干BeanDefinitionParser类,用于解析Xml元素,生成BeanDefinition。BeanDefinitionParser需要在NamespaceHandler中注册。
以上步骤也可以继承NamespaceHandlerSupport类,这个抽象类完成了NamespaceHandler的parse功能,也提供了注册BeanDefinitionParser的方法,是Webx使用的。
Webx的初始化
首先Webx重写了XmlWebApplicationContext。
public class XmlWebApplicationContext extends org.springframework.web.context.support.XmlWebApplicationContext
implements ResourceLoadingExtendable
同时实现了父类的initBeanDefinitionReader实现加入SpringExt相关的内容。一下这部分代码是主要的加载拓展点
和捐赠
的过程
public void addConfigurationPointsSupport() {
if (skipValidation) {
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
reader.setNamespaceAware(true); // 为了添加Configuration Point支持,namespace是必须打开的。
reader.setDocumentReaderClass(DocumentReaderSkippingValidation.class); // 对beans中的参数提供默认值
log.warn(
"XSD validation has been disabled according to the system property: -D{}. Please be warned: NEVER skipping validation in Production Environment.",
PROPERTY_SKIP_VALIDATION);
}
ResourceLoader resourceLoader = reader.getResourceLoader();
if (resourceLoader == null) {
resourceLoader = new DefaultResourceLoader();
}
ClassLoader classLoader = resourceLoader.getClassLoader();
// schema providers
SpringExtSchemaSet schemas = new SpringExtSchemaSet(classLoader);
// default resolvers
EntityResolver defaultEntityResolver = new ResourceEntityResolver(resourceLoader);
NamespaceHandlerResolver defaultNamespaceHandlerResolver = new DefaultNamespaceHandlerResolver(classLoader);
// new resolvers
EntityResolver entityResolver = new SchemaEntityResolver(defaultEntityResolver, schemas);
NamespaceHandlerResolver namespaceHandlerResolver = new ConfigurationPointNamespaceHandlerResolver(schemas.getConfigurationPoints(), defaultNamespaceHandlerResolver);
reader.setEntityResolver(entityResolver);
reader.setNamespaceHandlerResolver(namespaceHandlerResolver);
}
这部分有三个主要的步骤:
初始化的流程全部,包括注册
拓展点
和捐赠
:SpringExtSchemaSet schemas = new SpringExtSchemaSet(classLoader);
定义新的EntityResolver
EntityResolver entityResolver = new SchemaEntityResolver(defaultEntityResolver, schemas);
同时定义新的NamespaceHandlerResolver并使其能够取得SpringExt的NamespaceHandler的代码
NamespaceHandlerResolver namespaceHandlerResolver = new ConfigurationPointNamespaceHandlerResolver(schemas.getConfigurationPoints(), defaultNamespaceHandlerResolver);
SpringExtSchemaSet
别看只有一行构造方法,但是跟进去会发现别有洞天!
SchemaSet将一组Schemas
整合在一起的集合。那么是哪些Schemas
呢?
public SpringExtSchemaSet(ClassLoader classLoader) {
this(new ConfigurationPointsImpl(classLoader), new SpringPluggableSchemas(classLoader));
}
ConfigurationPointsImpl负责SpringExt的拓展特性的加载,而SpringPluggableSchemas则是将Spring所支持的META-INF/spring.schemas
中定义的schemas移到本地服务器。这两者都集成自Schemas
接口,这个接口只有一个方法就是获得一个Map
SchemaEntityResolver
主要是根据systemId获得其Schema的InputStream的过程,可对比Spring的DTD或者XSD的读取方法
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
log.trace("Trying to locate XML entity {} as configuration points schema.", systemId);
Schema schema = schemas.findSchema(systemId);
if (schema == null) {
if (defaultEntityResolver != null) {
return defaultEntityResolver.resolveEntity(publicId, systemId);
} else {
return null;
}
}
log.debug("Found XML schema for systemId {}: {}", systemId, schema);
return new InputSource(schema.getInputStream());
}
ConfigurationPointNamespaceHandlerResolver
ConfigurationPointImpl继承NamespaceHandlerSupport,主要是根据Namespace获得ConfigurationPoint
public NamespaceHandler resolve(String namespaceUri) {
ConfigurationPoint cp = cps.getConfigurationPointByNamespaceUri(namespaceUri);
if (cp != null) {
return cp.getNamespaceHandler();
} else if (defaultResolver != null) {
return defaultResolver.resolve(namespaceUri);
} else {
return null;
}
}
NamespaceHandlerSupport已经实现parse
方法,ConfigurationPoint包含了拓展点
的所有捐赠
,根据namespaceUri即可获得其具体的捐赠,也就是具体的BeanDefinitionParser