spring自定义标签

一、简介
  • spring在解析标签时分为默认标签和自定义标签两种;默认标签如bean标签,自定义标签如:aoptx(关于事物的)、dubbo(rpc框架的)。在一些复杂的业务场景下,普通bean无法满足需求;spring提供了可扩展Schema的支持,只需要我们实现部分逻辑就可以为我们解析自定义标签;spring自定义标签用于配置较为复杂或者需要丰富的控制的时候。
二、使用
  • 1、创建一个想要扩展的组件,其实就是创建一个bean(这里就用Dubbo简单模拟下,注意仅仅是模拟)
package com.zzq.provider.customtag.pojo;

public class Dubbo {
    /*spring beanName*/
	private String id;

	/*消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样*/
    private String name;

    /*使用zookeeper注册中心暴露服务地址 */
    private String address;

    /*使用zookeeper注册中心暴露服务地址*/
    private String protocol;

    /*生成远程服务代理,可以像使用本地bean一样使用demoService*/
    private String basePackage;

    /*消费者调用超时设置为10秒*/
    private String timeout;

    /*端口*/
    private String port;

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public String getTimeout() {
        return timeout;
    }

    public void setTimeout(String timeout) {
        this.timeout = timeout;
    }
}

  • 2、定义XSD文件描述组件内容(其实就是描述xml文件的) ,Spring-dubbo.xsd
 <?xml version="1.0" encoding="UTF-8"?>

<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lexueba.com/schema/dubbo"
        xmlns:tns="http://www.lexueba.com/schema/dubbo"
        elementFormDefault="qualified"

>

    <element name="dubbo">
        <complexType>
            <attribute name="id" type="string"></attribute>
            <attribute name="name" type="string"></attribute>
            <attribute name="address" type="string"></attribute>
            <attribute name="protocol" type="string"></attribute>
            <attribute name="port" type="string"></attribute>
            <attribute name="basePackage" type="string"></attribute>
            <attribute name="timeout" type="string"></attribute>
        </complexType>

    </element>
</schema>

  • 3、 实现BeanDefinitionParser接口或者继承AbstractSingleBeanDefinitionParser,用于解析XSD文件中的定义和组件的定义。如果您的业务需求比较复杂建议实现BeanDefinitionParser接口。

public class DubboBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

	// element对应的类
	@Override
	protected Class<?> getBeanClass(Element element) {
		return Dubbo.class;
	}

	// 从element中解析并提取对应的元素
	/*@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {
		String id = element.getAttribute("id");
		String address = element.getAttribute("address");
		String name = element.getAttribute("name");
		String protocol = element.getAttribute("protocol");
		String basePackage = element.getAttribute("basePackage");
		String timeout = element.getAttribute("timeout");
		String port = element.getAttribute("port");
		if (StringUtils.hasText(id)) {
			builder.addPropertyValue("id", id);
		}
		if (StringUtils.hasText(address)) {
			builder.addPropertyValue("address", address);
		}
		if (StringUtils.hasText(name)) {
			builder.addPropertyValue("name", name);
		}

		if (StringUtils.hasText(protocol)) {
			builder.addPropertyValue("protocol", protocol);
		}

		if (StringUtils.hasText(basePackage)) {
			builder.addPropertyValue("basePackage", basePackage);
		}

		if (StringUtils.hasText(timeout)) {
			builder.addPropertyValue("timeout", timeout);
		}

		if (StringUtils.hasText(port)) {
			builder.addPropertyValue("port", port);
		}
		System.out.println("id :" + id +" name : " +name +"  address :" +address);
	}*/


	@Override
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
		String id = element.getAttribute("id");
		String address = element.getAttribute("address");
		String name = element.getAttribute("name");
		String protocol = element.getAttribute("protocol");
		String basePackage = element.getAttribute("basePackage");
		String timeout = element.getAttribute("timeout");
		String port = element.getAttribute("port");
		if (StringUtils.hasText(id)) {
			builder.addPropertyValue("id", id);
		}
		if (StringUtils.hasText(address)) {
			builder.addPropertyValue("address", address);
		}
		if (StringUtils.hasText(name)) {
			builder.addPropertyValue("name", name);
		}

		if (StringUtils.hasText(protocol)) {
			builder.addPropertyValue("protocol", protocol);
		}

		if (StringUtils.hasText(basePackage)) {
			builder.addPropertyValue("basePackage", basePackage);
		}

		if (StringUtils.hasText(timeout)) {
			builder.addPropertyValue("timeout", timeout);
		}

		if (StringUtils.hasText(port)) {
			builder.addPropertyValue("port", port);
		}
		System.out.println("id :" + id +" name : " +name +"  address :" +address);
 
	}
 

}

  • 4、创建handler,也就是遇到这个Schema要进入自定义标签的处理类调init方法,扩展自NamespaceHandlerSupport,目的是为了注册遇到这个自定义要进入哪个实现。
package com.zzq.provider.customtag.handler;

import com.zzq.provider.customtag.parser.DubboBeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;


public class DubboNamespaceHandler extends NamespaceHandlerSupport {


    @Override
    public void init() {
        registerBeanDefinitionParser("dubbo",new DubboBeanDefinitionParser());
    }
}

  • 5、编写spring.handlers和spring.schemas文件,这个有点类似于SPI的形式

(1) spring.handlers

http\://www.lexueba.com/schema/dubbo=com.zzq.provider.customtag.handler.DubboNamespaceHandler

(2) spring.schemas

http\://www.lexueba.com/schema/dubbo.xsd=META-INF/Spring-dubbo.xsd
  • 6、编写配置文件,注意xml的头要写正确,而且要对应, application-customtag.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:myTagName="http://www.lexueba.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.lexueba.com/schema/dubbo http://www.lexueba.com/schema/dubbo.xsd">
        
 	<myTagName:dubbo id = "dubbo" name = "provider" address = "127.0.0.1:2181" protocol="zookeeper" basePackage="com.zzq.provider" port="8080"  timeout="10000" />
</beans>

然后在启动时入口的配置文件导入即可:

<import resource="application-customtag.xml"></import>
  • 7、完整的结构
    在这里插入图片描述

  • 8、运行结果
    在这里插入图片描述

三、源码解析
  • 定位到BeanDefinitionParserDelegate#parseCustomElement,这就是根据schema取对应的handler,也就是前面说的DubboNamespaceHandler这种
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
      // 获取对应命名空间
		String namespaceUri = getNamespaceURI(ele);
		// 例如namespaceUri为http://www.springframework.org/schema/aop, 所以得到的是 AopNamespaceHandler
		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));
	}
public NamespaceHandler resolve(String namespaceUri) {
		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;
			}
			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);
			}
		}
	}
private Map<String, Object> getHandlerMappings() {
		if (this.handlerMappings == null) {
			synchronized (this) {
				if (this.handlerMappings == null) {
					try {
						//载入全部项目的META-INF/spring.handlers(aop项目beans项目等等)配置文件得到对应关系如http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
						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;
	}
  • 此时已经取到了对应的handler,并且调用了其init方法。剩下的就是解析了。findParserForElement#parse ->findParserForElement

     private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		 		// 获取元素名称 <myTagName:dubbo> 中的dubbo  此时localName为dubbo
		String localName = parserContext.getDelegate().getLocalName(element);
		// 此时根据localName为dubbo找解析器 就是DubboBeanDefinitionParser 
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		}
		return parser;
	}

  • 然后再调用parse 方法,因为我是继承了AbstractSingleBeanDefinitionParser的,所以会进入AbstractBeanDefinitionParser#parse -> AbstractSingleBeanDefinitionParser#parseInternal
    最后AbstractSingleBeanDefinitionParser#doParse子类实现或者重写其中任意一个方法做具体实现。
    在这里插入图片描述
    //  重写这个也行就看您需不需要parserContext对象了
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
		//此处调用具体的实现类
		doParse(element, builder);
	}

posted on 2019-07-20 00:55  愤怒的苹果ext  阅读(35)  评论(0编辑  收藏  举报

导航