一、执行org.springframework.beans.factory.xml.PluggableSchemaResolver#getSchemaMappings获取所有的namespace 对应的schemas,即命令空间的解析模板xsd文件
这个方法会被spring自动触发,然后执行
PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
schemaMappingsLocation=META-INF/spring.schemas,这个就是获取classLoader路径下面所有包的META-INF/spring.schemas文件,然后对spring.schemas文件里面的Key=value,进行key合并。
恰好在dubbo的jar包里面,有个spring.schemas
二、执行org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings获取对应的hanlders
该方法会作用在SpringBoot的ImportResource注解,如下
@ImportResource(locations={"classpath:dubbo/*.xml"})
被org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromImportedResources方法触发loadBeanDefinitions。加载beanDefinition的时候,由于importResource传入的是dubbo的xml。那么又会触发解析xml的过程。如下:
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);
}
}
每个xml的标签都会被解析为Element,spring在转换xml的标签到beanDefinition的过程,使用如下代码:
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));
}
是不是很明显了,dubbo的标签语言为啥会被正确解析呢?就是因为使用了DubboNamespaceHandler,这个handler就是通过Element上的命名空间来路由的。
三、DubboNamespaceHandler的解析操作
handler.parse方法会调用,如下:
org.springframework.beans.factory.xml.NamespaceHandlerSupport#findParserForElement
这个方法会根据Element,去parsers里面找到一个具体的BeanDefinitionParser,它是一个接口。每个框架都可以自定义规范,只要框架实现了BeanDefinitionParser,那么该框架就可以把自定义格式的文件,转换为BeanDefinition,委托给Spring。
咱们看下DubboNamespaceHandler的parsers的map都有啥,如下:
image.png
看到没,key就是Dubbo框架自定义的语法,通过dubbo实现的DubboBeanDefinitionParser就可以把<service ref="xxx",interface="xxx" group="xxx"/> 这个格式的字符串,转换为beanDefiniton。不就类似spring bean的xml解析嘛。我自己也能任意定义,只要把它与beanDefinition的字段,对应好就没问题。
废话不多说,开始看最重要的部分,解析。
四、DubboBeanDefinitionParser的解析
先看下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 DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
每个dubbo标签,传入DubboBeanDefinitionParser的class都不一样。说明具体解析操作,肯定会根据class类型不同,获取标签不同的字段。
具体解析如下:
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
//省略
....
if (ProtocolConfig.class.equals(beanClass)) {
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
//省略
....
}
我们最关心的不就是ServiceBean和ReferenceBean(ConsumerConfig)。以ServiceBean为例,生成的BeanDefinition。
总结:
如果Spring的某一个bean被dubbo进行修饰了,那么spring容器里面会生成两个Bean。一个是spring的普通bean,一个是dubbo的serviceBean类型的bean。这个bean的初始化很简单,主要逻辑在serviceBean的export里面。
export的主要作用就是:
1、构造Invoker,其实就是把spring真正的bean对象进行代理。
2、暴露本地tcp端口,用于通信
3、保存<service,Invoker>到本地,便于上面通信模块接收服务的时候,找到正确的处理Invoker。
4、注册提供者到注册中心
5、向注册中心订阅变化
当然每个步骤都是细节满满,巨复杂。但是提供者的主流程就是上面。
网友评论