原理
解析自定义标签方法入口
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 这个方法先获取了xml元素对应的namespaceUri,
String namespaceUri = getNamespaceURI(ele);
// 然后找到该namespace对应的handler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 使用对应的handler进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
找handler这一步的resolve方法
public NamespaceHandler resolve(String namespaceUri) {
// 获取当前nameSpace对应的处理类
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();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
}
}
TIPS:上述代码中有Spring惯用的缓存方法,在map存放的对应关系中,如果未实例化则放入待实例化的内容,如果已经实例化了,则放入实例化之后的内容具体使用。在首次加载的过程中都是走到最下边的else,然后在后续使用的过程中通过前面的缓存信息判断,从而达到提升效率的作用。
获取handler的具体方法
详细阅读一下这个方法,其实就是加载 META-INF/spring.handlers(默认位置),转换成键值对的handlerMapping。比如说
http\://www.example.com/schema/user=org.example.test.custom.MyNameSpaceHandler
则会加载成put("http\://www.example.com/schema/user","org.example.test.custom.MyNameSpaceHandler")的形式,从而后续自定义的namespaceHandler可以使用。
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
// 没有加载过handlermappings
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
// 加载属性,默认位置是 META-INF/spring.handlers
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
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 handlerMappings;
}
在获取到的namespacehandler之后就可以使用对应的handler进行解析了,但是在解析的步骤中会出现一些重复的解析,比如id,name,alias等属性的解析,因此又提供了模版方法的机制,让自定义的handler只解析自定义的属性即可。
解析方法入口
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
获取parser方法
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
// parsers 就是map对象。
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
其实获取具体的parser就是很简单的从map中get,那么parser中的内容是何时塞进去的呢?还记得namespacehandler的实例化吧,可以往上面查一查,实例化会会调用init方法,那么我们只需要在调用init的时候注册一下就可以了。
初始化加载自定义的beanDefinition例子
@Override
public void init() {
// 初始化注册这个解析器
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
具体的解析过程
我们的parser本质上只需要实现BeanDefinitionParser接口即可,但是Spring在此基础上提供了几个抽象类,比如说AbstractBeanDefinitionParser解析了id,name,alias,我们只需要实现其模版方法parseInternal即可完成全部的解析以及聚合。
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
String id = resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
parserContext.getReaderContext().error(
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
catch (BeanDefinitionStoreException ex) {
parserContext.getReaderContext().error(ex.getMessage(), element);
return null;
}
}
return definition;
}
在parseInternal方法中我们可以对bean定义内容进行改写,当然我们需要知道bean定义该怎么定义。不在此赘述。
自定义标签的使用实战
自定义标签步骤总结
1、META-INF/spring.handlers 添加namespaceuri和handler对应的配置
2、添加自定义handler,并重写init方法完成注册
3、自定义解析器,重写parseInternal方法,并完成自定义标签的解析。
4、在xml文件中使用自定义标签
这个地方会使用到xsd校验。
dubbo中的自定义标签
dubbo provider xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="service-provider2" owner="xiaoming" />
<!-- 使用zookeeper注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" id="r1" timeout="10000"/>
<!-- 用dubbo协议在20882端口暴露服务
<dubbo:protocol name="dubbo" port="20882" /> -->
<!-- 用dubbo协议在20883端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20883" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.lagou.service.HelloService" ref="helloService" />
<!-- 和本地bean一样实现服务 -->
<bean id="helloService" class="com.lagou.service.impl.HelloServiceImpl" />
</beans>
可以看下dubbo的配置,其中在xml头文件中定义xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"自定义标签dubbo对应的命名空间是http://dubbo.apache.org/schema/dubbo,其次定义dubbo这个命名空间对应的xsd文件地址是http://dubbo.apache.org/schema/dubbo/dubbo.xsd
备注:xsd文件是用来验证xml的格式的。就比如dubbo的xsd文件可能定义dubbo标签下可以具备什么属性,什么元素等内容。包括idea、spring都会进行相关的校验。其解析文件可以通过本地
META-INF/spring.schemas 转换获取。比如dubbo定义了http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd,即可获取本地的定义文件进行xml校验。
dubbo源码下 META-INF/spring.handlers 定义
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
DubboNamespaceHandler中的init方法
public void init() {
this.registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
this.registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
this.registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
this.registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
this.registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
this.registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
this.registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
this.registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
this.registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
this.registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
this.registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
this.registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
this.registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
this.registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
对使用dubbo自定义标签的一系列元素均单独定义了解析器。
DubboBeanDefinitionParser的parse方法
dubbo的解析器是直接实现BeanDefinitionParser接口的,所以元素内的每一个属性都需要我们自己解析。看下parse的方法就是生成bean定义的过程。下面贴出来一部分。
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
String className;
int counter;