如何在Spring配置dubbo引用和服务
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:consumer check="false" />
<!-- 注入dubbo消费者xxService -->
<dubbo:reference id="xxService" interface="com.focuse.xx.xxService" timeout="5000"/>
<!-- 定义bean yyServiceImpl -->
<bean id="yyServiceImpl" class="com.focuse.yy.yyServiceImpl" />
<!-- 暴露yyService -->
<dubbo:service interface="com.focuse.api.yy.yyService"
ref="yyServiceImpl" timeout="3000" loadbalance="random" actives="0" />
</beans>
我们对上面的配置应该都不陌生,配置了一个dubbo引用xxService,使用的标签是<dubbo:reference>;另外还把yyService暴露出去,使用的标签是<dubbo:service>。
为什么Spring能够识别<dubbo:reference>、<dubbo:service>这样的标签,而不能识别<hello:print>? 我们能不能让Spring识别<hello:print>标签?答案是肯定的。
Spring中Schema扩展机制
Spring框架是个包容的框架,啥玩意都能集成进来。在Xml配置方面Spring也让开发者有很高的自主权。Spring默认提供2个Bean容器(ApplicationContext)从Xml加载Bean配置:XmlWebApplicationContext以及ClassPathXmlApplicationContext。不管是哪个都是用XmlBeanDefinitionReader来解析xml并往容器中注册Bean。以XmlWebApplicationContext为例:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
reader.loadBeanDefinitions(configLocation)调用的是AbstractBeanDefinitionReader的loadBeanDefinitions(String),最终会调用XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource)。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
......
}
doLoadBeanDefinitions首先是构造Document对象(xml解析用的是apache的xerces组件),然后调用registerBeanDefinitions注册Bean。继续往下跟,Spring是用DefaultBeanDefinitionDocumentReader来遍历Document的节点Node,DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
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;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这里很关键, 判断是否是默认namespace,如果不是默认namespace则调BeanDefinitionParserDelegate.parseCustomElement解析自定义的节点。
这里我们联想一下前面dubbo的配置:<dubbo:service>标签的namespace是dubbo(此处xml配置的是http://dubbo.apache.org/schema/dubbo),不是默认的namespace(此处xml配置的是http://www.springframework.org/schema/beans),所以dubbo解析就用调用BeanDefinitionParserDelegate.parseCustomElement。 ok! 我们继续!
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));
}
这里就是获取对应namespace的handler然后调用对应的parse方法解析Bean。
再往下,我们看一下如何获取namespace的对应handler的。我们看一下DefaultNamespaceHandlerResolver的resolve方法(this.readerContext.getNamespaceHandlerResolver获取的对象就是DefaultNamespaceHandlerResolver实例):
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 {
... ...
}
}
很明显,关键是在getHanderMappings里面,继续!
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
... ...(省略)
}
catch (IOException ex) {
... ...(省略)
}
}
}
}
return this.handlerMappings;
}
getHandlerMappings从相关文件去加载Properties,handlerMappingsLocation默认值是META-INF/spring.handlers
DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"
ok!到这里,Spring的Schema机制基本分析完了,一句话概述就是 Spring在读取xml配置时,如果标签的namespace不是xml默认的namespace,就会调用对应的handler的parse来解析BeanDefinition并注册。
既然已经知道Spring的Schema机制,我们来解答第一个问题:为什么Spring能够识别<dubbo:reference>、<dubbo:service>这样的标签。因为dubbo的jar定义了spring.handers,并解析该标签。有兴趣的可以看看DubboNamespaceHandler,继续跟踪Dubbo是如何启动的。
如何自定义Schema
根据前文自定义Schema需要2个元素:META-INF下创建spring.handers文件、定义NamesapceHandler实现类。其实,在加载xml的时候还需要校验标签,所以还需要2元素:META-INF创建spring.schemas、定义schema文件(.xsd)。以<hello:print>标签为例。
第一步 定义schema文件(hello.xsd),规范xml标签的类型、元素、属性等
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.focuse.com/schema/hello">
<xsd:element name="print">
<xsd:complexType>
<xsd:attribute name="text" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
这里我们只定义一个元素标签<print>,该元素标签包含一个属性"text"。 这里不赘述schema语法,具体的schema语法参考https://www.w3school.com.cn/schema/index.asp
第二步 META-INF创建2个文件 spring.handlers 和 spring.schemas
spring.handlers定义namespace对应的handler
http\://www.focuse.com/schema/hello=com.focuse.springschema.config.HelloNamespaceHandler
spring.schemas将schema文件URI指向本地文档。这里我们将www.focuse.com/shcema/hello.xsd指向到本地META-INF下的hello.xsd(就是我们第一步定义的schema文件)
http\://www.focuse.com/schema/hello.xsd=META-INF/hello.xsd
第三步 实现NamespaceHandler处理xml元素(element)
Spring为了方便开发者,提供了一个抽象类NamespaceHandlerSupport,开发者需要继承该抽象类。但是,在实现类里,开发者要定义单个标签的解析器(BeanDefinitionParser)。
public class HelloNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("print", new HelloBeanDefinitionParser());
}
}
因为我们第一步只定义了一个标签<print>,所以这里只注册"print"的解析器HelloBeanDefinitionParser。重点要看HelloBeanDefinitionParser做了什么
public class HelloBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
String text = element.getAttribute("text");
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setScope("singleton");//单例
bd.setBeanClassName("com.focuse.springschema.printer.StringPrinter");
bd.getPropertyValues().add("text", text); //设置属性
parserContext.getRegistry().registerBeanDefinition("com.focuse.springschema.printer.StringPrinter", bd);
return bd;
}
}
HelloBeanDefinitionParser必须实现BeanDefinitionParser,parse方法的参数element就是当前解析的元素,
- 获取element的"text"属性备用(第一步中我们定义了<print>标签是有"text"属性的)
- 创建GenericBeanDefinition对象,并设置单例、设置类名(com.focuse.springschema.printer.StringPrinter)、添加text属性
- 向Bean容器注册BeanDefinition
OK! 至此,我们已经自定义了Schema。目录结构如下:
接下来,我们需要在应用中使用。
com.focuse.springschema.printer.StringPrinter类定义如下:
public class StringPrinter {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String print() {
return text;
}
}
首先,我们定义Bean 配置,配置元素<hello:print>其中hello是namespace(看xml文件头部),并设置元素的属性text="hello sprint shcema"
<?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:hello="http://www.focuse.com/schema/hello"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.focuse.com/schema/hello http://www.focuse.com/schema/hello.xsd"
>
<hello:print text="hello spring schema" />
</beans>
然后,我们定义Controller,在controller里面,我们注入StringPrinter的。因为<hello:print>元素就是往容器中添加了StringPrinter的Bean对象,所以这里自动装配能找到。在hello这个方法里面就是返回stringPrinter.getText值
@RestController
@RequestMapping("/focuse")
public class SpringController {
@Resource
private StringPrinter stringPrinter;
@RequestMapping("hello")
public String hello() {
return stringPrinter.getText();
}
}
最后结果,就是输出"hello sprint schema"
总结
本文简单介绍了Spring Schema的机制,我们知道Spring是如何识别<dubbo:reference>的,一言以蔽之就是Spring在读取xml配置时,如果标签的namespace不是xml默认的namespace,就会调用对应的handler的parse来解析BeanDefinition并添加到Bean容器中。handler的定义默认配置在META-INF/spring.handlers中。
本文还介绍了自定义Schema的3个步骤:
- 定义Schema规范文件;
- META-INF创建spring.handers和spring.schemas;
- 实现NamespaceHandler处理xml元素(element)