最近在学习使用携程的Apollo配置中心,看到Apollo的自定义标签,学习一下。
关于spring的自定义标签教程很多,这里就不赘述了,直接记录一下Apollo是怎么样利用自定义标签来注入属性的。
新建apollo-1.0.0.xsd文件
这个xsd就定义了一个config节点,然后下面定义了两个属性,namespaces,order,这两个属性就是应用id和加载顺序。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.ctrip.com/schema/apollo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.ctrip.com/schema/apollo"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:annotation>
<xsd:documentation><![CDATA[ Namespace support for Ctrip Apollo Configuration Center. ]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="config">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ Apollo configuration section to integrate with Spring.]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="namespaces" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The comma-separated list of namespace names to integrate with Spring property sources.
If not specified, then default to application namespace.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:int" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The order of the config, default to Ordered.LOWEST_PRECEDENCE, which is Integer.MAX_VALUE.
If there are properties with the same name in different apollo configs, the config with smaller order wins.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
定义xmlns节点
我们知道在spring的配置文件里面,都是通过定义xmlns来引用标签的,如:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:qt="http://www.imobpay.com/schema/qt"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.imobpay.com/schema/qt http://www.imobpay.com/schema/qt.xsd"
default-autowire="byName" default-lazy-init="false">
新建一个spring.schemas
这里的http\://www.ctrip.com/schema/apollo-1.0.0.xsd
就是xmlns:qt
配置需要的。
http\://www.ctrip.com/schema/apollo-1.0.0.xsd=/META-INF/apollo-1.0.0.xsd
http\://www.ctrip.com/schema/apollo.xsd=/META-INF/apollo-1.0.0.xsd
自定义标签的解析类
package com.ctrip.framework.apollo.spring.config;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.core.Ordered;
import org.w3c.dom.Element;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
@Override
public void init() {
//定义自定义标签的标签名config,这些写死了config,因此使用的时候只能是config,使用的时候一般是这样的<apollo:config>
registerBeanDefinitionParser("config", new BeanParser());
}
static class BeanParser extends AbstractSingleBeanDefinitionParser {
//这个方法就是返回一个bean,这个bean会被spring加载进去。
@Override
protected Class<?> getBeanClass(Element element) {
return ConfigPropertySourcesProcessor.class;
}
//是否生产ID,一般都是true,可以看注释。
@Override
protected boolean shouldGenerateId() {
return true;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
//这段逻辑就比较简单了,就是解析自定义的标签,因为apollo只定义了一层标签,因此解析也比较简单,如果节点有子节点那解析就会复杂很多了,这里只是把namespaces标签的值取出来用","做了一个分割变成一个集合,然后判断order是否有值,没有就默认最小的。
//这里最关键的代码就是 PropertySourcesProcessor.addNamespaces(NAMESPACE_SPLITTER.splitToList(namespaces), order);这段代码了,这段代码就把namespace和order添加到PropertySourcesProcessor的一个内部属性里面了,这个内部属性就是一个常量map,下面这一点是PropertySourcesProcessor的方法。
==========================
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
public static boolean addNamespaces(Collection<String> namespaces, int order) {
return NAMESPACE_NAMES.putAll(order, namespaces);
}
============================
String namespaces = element.getAttribute("namespaces");
//default to application
if (Strings.isNullOrEmpty(namespaces)) {
namespaces = ConfigConsts.NAMESPACE_APPLICATION;
}
int order = Ordered.LOWEST_PRECEDENCE;
String orderAttribute = element.getAttribute("order");
if (!Strings.isNullOrEmpty(orderAttribute)) {
try {
order = Integer.parseInt(orderAttribute);
} catch (Throwable ex) {
throw new IllegalArgumentException(
String.format("Invalid order: %s for namespaces: %s", orderAttribute, namespaces));
}
}
PropertySourcesProcessor.addNamespaces(NAMESPACE_SPLITTER.splitToList(namespaces), order);
}
}
}
新建spring.handlers文件
上面定义好自定义标签解析器之后,还需要使spring识别这个自定义解析器,新建spring.handlers,里面的文件内容就是这样的,后面的就是自定义的标签解析类。
http\://www.ctrip.com/schema/apollo=com.ctrip.framework.apollo.spring.config.NamespaceHandler
这样一个完整的自定义标签解析就算完成了,然后打成jar包,项目引用标签就能自动进行解析了。