Dubbo和Spring的融合
通常,我们使用Dubbo的时候会创建如下的配置文件
<?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="hello-world-app" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://127.0.0.1:1234" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>
像dubbo:service这样的标签是如何添加到spring并且被识别的呢?
答案是它使用了Spring的xsd schema文档定义语言来实现的,首先我们自定义一套schema来熟悉一下Spring的可扩展Schema。
分为五个步骤
1.设计配置属性和Bean
2.编写xsd文件
3.使用NamespaceHandler和BeanDefinitionHandler来完成解析工作
4.配置spring.handlers和spring.schemas串联这些类
5.在spring中使用这个bean
(1)定义一个User类:
@Data
public class User {
String id;
private String name;
private String email;
private int age;
}
(2)编写xsd文件
文件名称为spring-test.xsd,名称可以自定义
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.fanfte.com/schema/user"
xmlns:tns="http://www.fanfte.com/schema/user" elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="age" type="int" />
<attribute name="name" type="string" />
<attribute name="email" type="string" />
</complexType>
</element>
</schema>
(3)使用NamespaceHandler和BeanDefinitionHandler来完成解析工作
首先编写bean解析器
public class UserBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String id = element.getAttribute("id");
String name = element.getAttribute("name");
String email = element.getAttribute("email");
Integer age = Integer.parseInt(element.getAttribute("age"));
if(StringUtils.hasText(id)) {
builder.addPropertyValue("id", id);
}
if(StringUtils.hasText(name)) {
builder.addPropertyValue("name", name);
}
if(StringUtils.hasText(email)) {
builder.addPropertyValue("email", email);
}
if(age != null) {
builder.addPropertyValue("age", age);
}
}
}
再编写命名空间处理器:
public class UserNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
(4)配置spring.handlers和spring.schemas串联这些类
再项目的classpath下的META-INF下配置两个文件:
spring.handlers
http\://www.fanfte.com/schema/user=com.fanfte.rpc.spring.UserNameSpaceHandler
spring.schemas
http\://www.fanfte.com/schema/user.xsd=META-INF/spring-text.xsd
这里的路径对应了第二步xsd文件中的路径,类名对应了命名空间解析器的全路径名com.fanfte.rpc.spring.UserNameSpaceHandler
(5)配置和加载bean到spring容器
classpath下新建一个application-tag.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:userTag="http://www.fanfte.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.fanfte.com/schema/user http://www.fanfte.com/schema/user.xsd">
<userTag:user id="userTag" name="userBean" email="fanfte@163.com" age="18"/>
<!--<fanfteRPC:reference id="demoService" interface="com.fanfte.rpc.service.DemoService"></fanfteRPC:reference>-->
</beans>
xml中使用配置了的bean ,标签名称为userTag,对应了文件中的第四行的xmlns:userTag
最后,在main方法中使用这个bean
public class UserTagTest {
@Autowired
private DemoService demoService;
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:META-INF/application-tag.xml");
User user = (User) context.getBean("userTag");
System.out.println(user.getAge() + " " + user.getEmail() + " " + user.getName());
}
}
这样就使用了自定义的xsd来配置使用我们自定义的标签。dubbo也有一套自己的自定义标签。
Dubbo融合Spring
<1>首先我们看到DubboNamespaceHandler这个类内部
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
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 AnnotationBeanDefinitionParser());
}
}
这个类就是Dubbo的命名空间处理器,可以看到有10种的命名空间,而定义的配置属性也定义在了xxxConfig和xxxBean里面,而解析器则使用了DubboBeanDefinitionParser和AnnotationBeanDefinitionParser两种。
从这个类可以看到已经完成了dubbo自定义schema完成了上面的第(1)和(3)步工作
<2>第二部我们可以在dubbo.xsd文件种发现
<xsd:element name="service" type="serviceType">
<xsd:annotation>
<xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.apache.dubbo.config.ServiceConfig"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="reference" type="referenceType">
<xsd:annotation>
<xsd:documentation><![CDATA[ Reference service config ]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.apache.dubbo.config.ReferenceConfig"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
这里贴出来部分代码,做了多种的xsd元素标签配置。
<3>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
<4>spring.schemas
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
这两个文件串联了Dubbo的整个csd的解析
最后,在applicationContext这类xml文件中我们使用dubbo的标签让spring管理我们定义的ServiceBean。
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
总结:
1.通过上述5个步骤,我们实现了自定义的标签
2.通过观察Dubbo源码中的逻辑和配置,发现Dubbo从原理上也是这么实现它的自定义标签的,对于Spring的融合能够完美支持。其实也就是利用了Spring的特性。