二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);
}
}
上述解析的流程如下:
在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)