Spring(一) 自定义标签的原理以及dubbo应用

原理

解析自定义标签方法入口

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值