二Dubbo设计基础--5Dubbo与Spring的融合

二Dubbo设计基础--5Dubbo与Spring的融合

2.5.1 标签解析原理

spring使用过程会遇到各种标签元素进行解析,该功能与spring核心组件有关。而核心组件中springAOP、springBean、springContext都包含spring.handlers文件,文件内容表明了他们能够解析的xml元素。

(springframework提供了IOC与aop功能,核心组件有五个,SpringAop、SpringBean、SpringContext、SpringAspect和SpringCore)

三大模块组件的handlers文件如下:

spring-aop的spring.handlers文件如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

spring-bean如下:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

Spring-Context如下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

以xml解析为例,分析:

ApplicationContext bc =new ClassPathXmlApplicationContext("./resources/spring-common.xml");

其中xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>  

//beans为默认的配置根元素,不需要特别配置
//此处uri地址为nameSpaceUri
<beans xmlns="http://www.springframework.org/schema/beans"  
       
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-4.2.xsd">

     <context:property-placeholder location="classpath:foo.properties" />

     <bean id="person" class="com.nuaa.SpringXMLConfig.Person"> 
          <property name="name" value="helloworldwangjy"></property>
          <property name="year" value="100"></property>
     </bean> 
</beans>

其中,new ClassPathXmlApplicationContext("./resources/spring-common.xml") 最终会调用DefaultBeanDefinitionDocumentReader的parseBeanDefinitions函数。——(解析beandefinition核心入口)

主要的解析类DefaultBeanDefinitionDocumentReader

//解析xml
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
     //判断root是否为defaultNamespace(是否为bean节点)
     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;
                    //判断ele是否为bean节点
                    if (delegate.isDefaultNamespace(ele)) {
                         //对bean的节点属性进行解析
                         parseDefaultElement(ele, delegate);
                    }
                    else {
                         //如果不是bean节点,用handlers文件内配置的解析类进行解析
                         delegate.parseCustomElement(ele);
                    }
               }
          }
     }
     else {
          //同上
          delegate.parseCustomElement(root);
     }
}

上述解析的流程如下:

image-20210802173337334

在BeanDefinitionParserDelegate中存在函数如下:

public boolean isDefaultNamespace(String namespaceUri) {
     return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}

public boolean isDefaultNamespace(Node node) {
     return isDefaultNamespace(getNamespaceURI(node));
}

BEANS_NAMESPACE_URI的定义如下:

public static final String BEANS_NAMESPACE_URI ="http://www.springframework.org/schema/beans";

判断是否为bean节点依据,看node的namespaceUri是否为BEANS_NAMESPACE_URI。

如果是isDefaultNamespace返回true,就进入函数parseDefaultElement(ele, delegate),如果不是则进入函数delegate.parseCustomElement(ele),用handlers文件内配置的解析类进行解析。

parseDefaultElement函数中,则分别读取import 属性、alias属性 bean属性或者beans属性进行解析。

//bean节点的解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
     if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
          importBeanDefinitionResource(ele);
     }
     else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
          processAliasRegistration(ele);
     }
     else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
          processBeanDefinition(ele, delegate);
     }
     else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
          // recurse
          doRegisterBeanDefinitions(ele);
     }
}

如果不是bean节点,需要用custom的handlers内配置的解析类进行解析

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));
}

首先获取元素的nameSpaceUri,然后根据nameSpaceUri获取指定的NamespaceHandler进行解析,

譬如property-placeholder元素,其配置如下:

<context:property-placeholder location="classpath:foo.properties" />

namespaceUri的值就是 http://www.springframework.org/schema/context

this.readerContext.getNamespaceHandlerResolver()获取到的是NamespaceHandlerResolver的实现类,为DefaultNamespaceHandlerResolver。

DefaultNamespaceHandlerResolver中包含了一个handlerMappings,

private volatile Map<String, Object> handlerMappings;

handlerMappings其中存储了所有的nameSpaceUri及其对应的NamespaceHandler.

handlerMappings内容就是spring三个handlers内的值,http://www.springframework.org/schema/context的对应的解析类是org.springframework.context.config.ContextNamespaceHandler。获取到后就由org.springframework.context.config.ContextNamespaceHandler解析对应的元素。

2.5.2 Dubbo标签解析步骤

以Dubbo引入spring为例,分析Dubbo怎么通过设置配置相关信息,使得spring能够解析Dubbo命名空间的配置。

这里,dubbo仅仅是一个空间命名,不是实现类或者spring中容器的对象。dubbo命名空间下,可以新建一个或者多个对象。

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"

       //dubbo的命名空间地址(xmlns:指dubbo的xml命名空间nameSpace地址namespaceURI)
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
                           
       //指定dubbo的namespaceURI以及对应的dubbo.xsd对dubbo的描述文件
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
  
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
  
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />

    <!--具体实现该接口的 bean-->
    <bean id="demoService" class="com.alibaba.dubbo.demo.impl.DemoServiceImpl"/>
</beans>

作为分析,dubbo命名空间下的其他配置删除,仅以protocol的配置为例,分析需要实现的配置以及如果开发遇到,开发的顺序。

step1 定义模型类

例如,这里的dubbo:protocol。dubbo命名空间下的protocol,其内配置了protocol的属性,因此,需要定义其模型类,在这里,是ProtocolConfig

public class ProtocolConfig extends AbstractConfig {
    // 服务协议
    private String              name;

    // 服务端口
    private Integer             port;
    //构造函数
    public ProtocolConfig(String name, int port) {
        setName(name);
        setPort(port);
    }

因此,protocol只需要配置name、port属性,就可以创建配置类。

step2 定义xsd文件

作用:xsd文件,制定模型(如protocol)属性,包括定义属性的名称、数据类型等信息。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

//定义dubbo的namespaceURI信息
<xsd:schema xmlns="http://code.alibabatech.com/schema/dubbo"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:tool="http://www.springframework.org/schema/tool"
   //该值应该和上面那个相同
	targetNamespace="http://code.alibabatech.com/schema/dubbo">
	
	<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
	<xsd:import namespace="http://www.springframework.org/schema/beans"/>
	<xsd:import namespace="http://www.springframework.org/schema/tool"/>
  
  //定义以一个复杂类型,名称为“protocolType",该值仅仅是为complexType的定义名
  <xsd:complexType name="protocolType">
		<xsd:sequence minOccurs="0" maxOccurs="unbounded">
			<xsd:element ref="parameter" minOccurs="0" maxOccurs="unbounded" />
		</xsd:sequence>
		//由于protocolConfig构造函数,只需要name和port就可以初始化,因此这里定义xml内的配置属性为这两个
		<xsd:attribute name="name" type="xsd:string" use="optional">
			<xsd:annotation>
        //documentation是对这个属性的注释解释
				<xsd:documentation><![CDATA[ The protocol name. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="port" type="xsd:string" use="optional">
			<xsd:annotation>
				<xsd:documentation><![CDATA[ The service port. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
    
    <xsd:attribute name="host" type="xsd:string" use="optional">
			<xsd:annotation>
				<xsd:documentation><![CDATA[ The service host. ]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
</xsd:complexType>

 		//此处element,定义了一个元素,名称为“protocol”,类型为前面定义的复杂类型(这里的name就是xml中dubbo:protocol)
  <xsd:element name="protocol" type="protocolType">
		<xsd:annotation> 
			<xsd:documentation><![CDATA[ Service provider config ]]></xsd:documentation> 
		</xsd:annotation>
	</xsd:element>

dubbo.xsd文件,定义xml在dubbo:protocol的配置内容,以及配置名称protocol。

step3 编写spring.schemas

作用:该文件用来指定xsd文件位置。(该文件放在META-INF文件夹下)

前一部分是定义dubbo命名空间当前其namespaceURI,后面是命名空间的具体描述文件位置。

http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd

注意http://code.alibabatech.com/schema/dubbo部分要与xsd文件中的targetNamespace相同

step4 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"

//dubbo的命名空间地址(xmlns:指dubbo的xml命名空间nameSpace地址namespaceURI)
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
         
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
                           
//指定dubbo的namespaceURI以及对应的dubbo框架下,spring.schemas中的dubbo.xsd(对dubbo的描述文件)
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> 
  
//该处,配置的属性,是依据xsd中定义规则
    <dubbo:protocol name="dubbo" port="20880" />

step5 编写spring.handlers文件

作用:主要用于关联,命名空间namespaceURI和对配置文件xml中protocol属性的解析以及处理的处理器——NamespaceHandler

命名空间=具体的命名空间处理器namespaceHandler

该文件放在spring.schema同位置的META-INF文件夹下。

http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

step6 编写命名空间处理器( NamespaceHandlerSupport)

调用父类的registerBeanDefinitionParser方法,该方法有两个参数:

protocol:传入dubbo:protocol中的protocol(该处protocol参数,用于在后面解析过程中,解析该名称下的属性及值,构造beandefinition)以及构造parser;

DubboBeanDefinitionParser:该类是对protocol配置信息的具体parser类,通过构建beandefinition,然后注册入beanDefinitionRegistry内,传入的ProtocolConfig.class是构造对象的目标类。

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

	static {
		Version.checkDuplicate(DubboNamespaceHandler.class);
	}

	public void init() {
	      registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    //调用父类的方法
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

}

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
		//缓存,保存elementName以及解析类
	private final Map<String, BeanDefinitionParser> parsers =
			new HashMap<String, BeanDefinitionParser>();
//父类方法
	//传入
	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		this.parsers.put(elementName, parser);
	}

step7 编写BeanDefinition解析器( BeanDefinitionParser)

作用:解析自定义的xml标签,需要重写Parser内的方法parse(Element element, ParserContext parserContext),构造beandefinition,然后从element中获取属性,然后加入beanDefinitionRegistry中

public class DubboBeanDefinitionParser implements BeanDefinitionParser {
  		//需要创建的ProtocolConfig类
    private final Class<?> beanClass;
    
  		//解析protocol元素下,配置的属性
     private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
       //构建ProtocolConfig类的beanDefinition
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
       
						//注册到beandefinitionRegistry上
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
       			//获取element中属性,构造beandefinition
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }

        if (parameters != null) {
            beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
        }
  			//返回根据protocol构建好的ProtocolConfig的beandefinition
        return beanDefinition;
    

step8 测试

 7 public class Main {
 8     public static void main(String[] args) {
 9         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("dubbo.xml");
10         ProtocolConfig pc = (Protocol) applicationContext.getBean(Protoclo.class.getName());
11         System.out.println("name: " + pc.getName() + " port: " + pc.getPort());
12     }
13 }

如何在spring中自定义xml标签的方法就结束了。在实际中,随着注解和javaconfg的盛行,xml的方式渐渐的会淡出舞台,但是spring的启动流程还是会的。

bean的解析流程:

  • 使用ResourceLoader将配置文件xml装载为Resource对象;
  • 使用BeanDefinitionReader解析配置信息:将每一个解析为一个BeanDefinition对象,然后存储到BeanDefinitionRegistry中
  • 实际上是BeanDefinitionReader调用BeanDefinitionParser进行了解析操作,解析完成后注册到BeanDefinitionRegistry(代码看上边的HeroBeanDefinitionParser)
posted @ 2023-03-13 14:37  LeasonXue  阅读(84)  评论(0编辑  收藏  举报