Spring标签的黑魔法
如果你JAVAer,那么肯定对Spring的XML的各种标签的配置比较熟悉。我们先来复习一下,所谓温故知新吗。
- 当需要声明式事务支持的时候
<tx:annotation-driven/> //就是这么easy
- 当需要Spring自动扫描Bean的时候
<context:component-scan base-package="xxx.xxx" />
- 当需要把数据库的配置单独放在一个properties中的时候
<context:property-placeholder location="classpath:/xxx.properties"/>
如果你是初学Spring的话,你很可能被这些稀奇古怪的标签搞晕了头。因为这种配置实在是太多了。而且很可能在之前是那么配置的,过一段时间之后就那么配置了。这里举一个将数据库配置放在properties中的例子。
- 旧的配置
<bean id="propertyPlaceholderConfigurer" class="org.springframework,beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties<value/>
</list>
</property>
</bean>
- 新的配置
<context:property-placeholder location="classpath:/jdbc.properties"/>
你可能会有疑问,为什么新的版本里可以这么配,难道底层的实现变了吗?
这里先搁置该疑问,后面再一一解答。
其实Spring中还有好多这种带黑魔法的配置,虽然很黑科技,这里就不一一列举了。毕竟它是Spring!
其他开源框架和Spring整合的标签的黑魔法
如果说Spring中自带的黑魔法配置你不以为然,那么其他开源框架中的黑魔法配置又是怎么一回事呢?
先来看几个开源框架的Spring中的配置
1.mongodb的java客户端
<mongo:mongo id="mongo" host="${mongo.host}" port="${mongo.port}">
<mongo:options
connections-per-host="${mongo.connectionsPerHost}"
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
write-fsync="${mongo.writeFsync}"/>
</mongo:mongo>
2.携程开源的配置管理中心 点击这里
<apollo:config/>
3.当当开源的分布式任务调度框架elastic-job
<job:dataflow id="icSyncBaseDataJob" class="xxx.xxxx" streaming-process="true" registry-center-ref="regCenter" cron="0 0 2 * * ?" sharding-total-count="3" overwrite="true">
<job:listener class="xxx.xxxx" />
</job:dataflow>
可以看到每一个开源框架都有自己独有的命名空间(NameSpace),有没有感觉很吊!
那么作为一个有理想有抱负的程序员,你肯定会想如果有一天我自己开源了一个框架,并且要和Spring做集成的时候,我自己是不是也可以自定义属于自己的Spring标签(命名空间)呢?
如何自定义Spring的标签
自定义标签的流程我这里就不详细些了,这里推荐下面几篇文章
spring自定义标签
spring自定义标签之一 —— 意义思考
spring自定义标签之二 —— 规范定义XSD
spring自定义标签之三 —— 自我实现扩展Spring的几种方式
扩展Spring的几种方式
自定义Spring标签的原理
看完上面的自定义标签的流程,你是不是有疑问,为什么这样做就可以定义自己的标签。
其实这都要归功于Spring在设计之初就一贯坚持的设计原则:开闭原则:对扩展开放(Open for extension),对修改关闭(Closed for modification)。所以你可以在不修改Spring源代码的情况下扩展Spring的功能。
下面我们我们就来看一下Spring对应XML标签的处理
public class DefaultBeanDefinitionDocumentReader{
// 解析xml标签
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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;
// Spring默认的bean配置
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
// 用户自定义的bean配置
else {
delegate.parseCustomElement(ele);
}
}
}
}
// 用户自定义的bean配置
else {
delegate.parseCustomElement(root);
}
}
//其他方法。。。。
}
上面的代码逻辑还是很清晰的,一种处理默认标签,如
<bean "id=test" class="com.Test">
另一类是自定义的。如下
<tx:annotation-driven/>
下面我们来看一下自定义标签的解析原理
public class BeanDefinitionParserDelegate {
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
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.获取到命名空间
- 2.根据命名空间找到对应的命名空间处理器
- 3.根据用户自定义的处理器进行解析
第一步很简单,我们跳过。我们来看一下第二步的实现。
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
/**
* Locate the {@link NamespaceHandler} for the supplied namespace URI
* from the configured mappings.
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@Override
public NamespaceHandler resolve(String namespaceUri) {
// 获取所有已经配置的handler映射
Map<String, Object> handlerMappings = getHandlerMappings();
// 根据命名空间获取对应的信息
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
// 已经做过解析的直接从缓存中获取
return (NamespaceHandler) handlerOrClassName;
}
else {
// 没有做过解析的,则返回的是类路径
String className = (String) handlerOrClassName;
try {
// 使用反射将类路径转化为类
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 初始化处理器
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 调用自定义的NameSpaceHandler的init方法
namespaceHandler.init();
// 存入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map<String, Object> getHandlerMappings() {
// 如果没有缓存则开始进行缓存
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
// this.handlerMappingsLocation在构造函数中初始化为META-INF/spring.handlers
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return this.handlerMappings;
}
}
现在我们知道了为什么需要在META-INF/spring.handlers文件中那样配置了。因为在加载所有的处理器的配置的默认目录就是META-INF/spring.handlers
当获取到自定义的NameSpaceHandler之后就可以进行处理器初始化并解析了。实际上就是调用init方法。我们之前使用自定义NameSpaceHandler的时候就用到了。在init方法中注入解析器如下
public class AopNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
* '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
* and '{@code scoped-proxy}' tags.
*/
@Override
public void init() {
// In 2.0 XSD as well as in 2.1 XSD.
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
}
}
到现在为止,我们算是真正知道了怎么自定义新的XML标签,并且实现一个自定义的NameSpaceHandler。而且我么也知道了为什么要这么做。所以总结起来就是一句话
源代码面前了无秘密