上一篇我们说到如何找切入点,也给大家伙稍微的提了一点关于Spring自定义标签解析的知识。这篇我们继续来进行Dubbo服务注册的讲解。该篇内容主要来谈谈Dubbo配置的解析。说到底,这一篇还是在分析一些Spring的代码,毕竟我们要知其所以然,不然突然就说是从这开始的,这不让人懵逼嘛。
前言
Fix Bug
在上一篇在最后解释registerBeanDefinitionParser
时有点错误,现已经改正,标签都是在DubboBeanDefinitionParser
的parse()
方法中进行解析,解析操作和DubboBeanDefinitionParser
构造方法的第一个参数稍稍有点关系,但是并不是在第一个参数的类中里面进行解析
回顾
在上一节,我们首先通过Spring官方文档了解到如果要解析自定义的xml标签需要哪些步骤,然后通过关键的一步:需要在/META-INF/spring.handlers
和/META-INF/spring.schemas
中配置解析自定义标签的类以及schema
的位置,找到了dubbo解析自定义标签的类:org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
,并且也稍微看了下里面的init()
方法。我们发现啊,在init()
方法中,有几个注册的DubboBeanDefinitionParser
和其他的不同,如下图:
我们发现,其他的都是xxxConfig
,就这三个是xxxBean
。是有什么特殊的含义么?我们暂且不谈,先挖个坑,等后面再填,因为两个主线任务<dubbo:service>
和<dubbo:refernce>
都在里面,所以等后面分析时,我们还会再看回这里。
好了,回顾就到这里,开始今天的内容。
Dubbo中的配置实体解析
我们先聊聊Dubbo中配置实体,因为后续会用到。那么,有哪些呢?细心的大佬们肯定会发现,上面的图中不就有什么xxxConfig
么?没错啦,这些就是Dubbo中的配置实体。我们随便点开一个看看其结构吧。就拿最近的ApplicationConfig
来说吧,它内部的代码如下(因为篇幅太长,所以只贴一下属性):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public class ApplicationConfig extends AbstractConfig { private static final long serialVersionUID = 5508512956753757169L ; private String name; private String version; private String owner; private String organization; private String architecture; private String environment; private String compiler; private String logger; private List<RegistryConfig> registries; private String registryIds; private MonitorConfig monitor; private Boolean isDefault; private String dumpDirectory; private Boolean qosEnable; private Integer qosPort; private Boolean qosAcceptForeignIp; private Map<String, String> parameters; private String shutwait; }
可以看到我们<dubbo:application>
标签中配置的属性都可以在上面找到。那我们在看一眼他的类图吧。
我们发现,貌似所有的Config都会继承这个AbstractConfig
。在该类中,定义了一些公共的方法,比如toString()
。
在回顾里面,我们说有三个特殊的类,分别是ConfigCenterBean
/ServiceBean
/ReferenceBean
。那么这三个又是如何接收配置的呢?我们分别来看看他们的类声明。
ConfigCenterBean
1 2 3 public class ConfigCenterBean extends ConfigCenterConfig implements ApplicationContextAware , DisposableBean , EnvironmentAware { }
ServiceBean
1 2 3 4 public class ServiceBean <T > extends ServiceConfig <T > implements InitializingBean , DisposableBean ,ApplicationContextAware , ApplicationListener <ContextRefreshedEvent >, BeanNameAware ,ApplicationEventPublisherAware {}
ReferenceBean
1 2 public class ReferenceBean <T > extends ReferenceConfig <T > implements FactoryBean , ApplicationContextAware , InitializingBean , DisposableBean {}
如上所见,他们每个都继承了一个xxxConfig
类,xxxConfig
类中和ApplicationConfig
类一样,会在内部定义和对应xml标签配置的相关属性来接收配置信息,然后这些Config类都会继承AbstarctConfig
。这一切似乎很明白了。
那么下一步,我们来说说Dubbo配置解析的流程
Dubbo的配置解析流程
关于Spring的xml解析,之前我有写过相关的文章,见[Spring源码深度解析读书笔记(2)](http://blog.zyzling.top/2018/12/20/Spring%E6%BA%90%E7%A0%81%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0(2)/ ,这次我们就不在浪费时间在这上面了,我们直接来分析Dubbo的配置解析,当然啦,里面还是会穿插Spring的知识在里面,毕竟底层还是用Spring来做解析。下面就让我们开始吧。
首先我们从DubboNamespaceHandler
的init()
方法入手,也就是这张图
但从这段代码上来看,我们发现啊,它会调用registerBeanDefinitionParser()
方法,为每个标签注册一个BeanDefinitionParser
,那我们先从里面看起,先看看DubboBeanDefinitionParser
是个什么鬼。
DubboBeanDefinitionParser
我们先看它的类声明。因为代码有300多行,如果全贴出来也不现实,所以我们只会贴一些关键的地方。
1 2 3 4 5 6 7 public class DubboBeanDefinitionParser implements BeanDefinitionParser { @Override public BeanDefinition parse (Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass, required); } }
抛却其他逻辑不讲,首先DubboBeanDefinitionParser
是实现了Spring的BeanDefinitionParser
接口,并且实现了parse()
方法。我们可以看到在parse()
方法里,会调用另外一个重载的parse()
方法,并且会多传两个参数,一个是beanClass
,一个是required
,看上去这两个参数是成员属性,那么他是怎么定义和赋值的呢?见下面:
1 2 3 4 5 6 7 8 9 public class DubboBeanDefinitionParser implements BeanDefinitionParser { private final Class<?> beanClass; private final boolean required; public DubboBeanDefinitionParser (Class<?> beanClass, boolean required) { this .beanClass = beanClass; this .required = required; } }
从上面的代码,我们就可以很快的联想到在上面图片中的new DubboBeanDefinitionParser(ApplicationConfig.class, true)
是干什么的了。那好,我们的DubboBeanDefinitionParser
就先到此为止,放心,待会还会回来的~~
既然DubboBeanDefinitionParser
我们有了了解,那么就看外层的registerBeanDefinitionParser()
方法吧。这个方法是Spring中的NamespaceHandlerSupport
这个类的,那我们所幸就先看看这个类吧。
NamespaceHandlerSupport
首先贴一下关键的地方代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class NamespaceHandlerSupport implements NamespaceHandler { private final Map<String, BeanDefinitionParser> parsers = new HashMap(); private final Map<String, BeanDefinitionDecorator> decorators = new HashMap(); private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap(); public NamespaceHandlerSupport () { } @Override public BeanDefinition parse (Element element, ParserContext parserContext) { return this .findParserForElement(element, parserContext).parse(element, parserContext); } @Override public BeanDefinitionHolder decorate (Node node, BeanDefinitionHolder definition, ParserContext parserContext) { return this .findDecoratorForNode(node, parserContext).decorate(node, definition, parserContext); } void init () ; }
从上往下看,首先该类实现了NamespaceHandler
接口,然后实现了其中的parse()
和decorate()
方法,并且留了一个init()
方法给NamespaceHandlerSupport
的子类去实现,这也是我们DubboNamespaceHandler
中的init()
方法。
接着,内部定义了3个Map。这里我们只看第一个Map,也就是Map<String, BeanDefinitionParser> parsers = new HashMap()
.从变量名上看,这里应该存的是我们的xml解析类键值对,然后Key
肯定是标签名,而Value
则是具体对应的解析类。
那么NamespaceHandlerSupport
全貌我们已经有所了解了。现在就来看看其内部的registerBeanDefinitionParser()
方法。
registerBeanDefinitionParser(String,BeanDefinitionParser)
该方法内部就一行代码。如下:
1 2 3 protected final void registerBeanDefinitionParser (String elementName, BeanDefinitionParser parser) { this .parsers.put(elementName, parser); }
这里的this.parsers
就是开头我们说的Map<String, BeanDefinitionParser>
。也就是说,这里的register
只是把我们创建的DubboBeanDefinitionParser
和对应的标签名保存到Map中。
到这里,我们DubboNamespaceHandler
的init()
方法明面上的代码都已经解释完毕了。接下来我们就先来静态分析一波解析的代码(这时候就要靠猜想了),然后在使用调试的方式验证下我们的猜想。
既然我们上面说,registerBeanDefinitionParser()
只是把解析类放到Map中,那么肯定会有地方调用吧,而且我们Map的变量名是parses
。然后我们在上面说过,这个抽象类实现了NamespaceHandler
,并且也实现了其方法parse()
,顾名思义,我们盲猜待会解析xml的时候,肯定会调用这个方法。既然这样,我们就来看看parse()
方法吧。
parse(Element,ParserContext)
1 2 3 4 @Override public BeanDefinition parse (Element element, ParserContext parserContext) { return findParserForElement(element, parserContext).parse(element, parserContext); }
这里分成2步:
findParserForElement()
parse()
我们先看findParserForElement()
得到的是个什么鬼,才能继续往下看parse()
方法
findParserForElement(Element,ParserContext)
1 2 3 4 5 6 7 8 9 10 11 12 private BeanDefinitionParser findParserForElement (Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this .parsers.get(localName); if (parser == null ) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]" , element); } return parser; }
通过上面的代码,我们可以很清晰的知道,这个BeanDefinitionParser
是从哪里获取的,并且值是什么了。这一切都串起来了。当然,这还只是我们的猜想,静态分析得到的结果,具体是不是这样,待会我们动态调试一下就可以立马知晓。
到了这,我们的parse(Element,ParserContext)
方法的第二步parse()
方法就可以继续看下去了。因为我们已经知道了通过findParserForElement(Element,ParserContext)
得出来的是个什么鬼了。
既然知道是什么了,我们就直接来到其内部吧,也就是DubboBeanDefinitionParser#parse(Element, ParserContext)
DubboBeanDefinitionParser
上面说这个类的时候,我就说过,我们会回来的,毕竟前面都只是上层类,该类才是真正落地的实现类。
下面就让我们来看看它的parse(Element element, ParserContext parserContext)
方法
parse(Element,ParserContext)
1 2 3 4 5 6 7 @Override public BeanDefinition parse (Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass, required); }
也是调用内部的重载的方法parse(element, parserContext, beanClass, required)
parse(Element,ParserContext,Class<?>,boolean)
鉴于该方法篇幅有点长,并且都是一些对配置的解析,所以我们会省略其解析部分,并且分段来展示。然后又因为在某些地方,因为都是获取的父类或者接口类,所以我们不知道其具体实现类是什么,这时候就需要借助动态断点调试的方式来分析。
第一部分 准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false ); String id = element.getAttribute("id" ); if (StringUtils.isEmpty(id) && required) { String generatedBeanName = element.getAttribute("name" ); if (StringUtils.isEmpty(generatedBeanName)) { if (ProtocolConfig.class .equals (beanClass )) { generatedBeanName = "dubbo" ; } else { generatedBeanName = element.getAttribute("interface" ); } } if (StringUtils.isEmpty(generatedBeanName)) { generatedBeanName = beanClass.getName(); } id = generatedBeanName; int counter = 2 ; while (parserContext.getRegistry().containsBeanDefinition(id)) { id = generatedBeanName + (counter++); } } if (id != null && id.length() > 0 ) { if (parserContext.getRegistry().containsBeanDefinition(id)) { throw new IllegalStateException("Duplicate spring bean id " + id); } parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); beanDefinition.getPropertyValues().addPropertyValue("id" , id); } }
从第一部分代码来看,无非就是读取标签中的id
属性值。然后以id
值为key,把RootBeanDefinition
注册到一个地方。注意哦,我们是一个标签一个配置实体的,从DubboNamespaceHandler#init()
方法中也可以看到。
第二部分 兼容之前的代码?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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这个if分支来说,class
这个属性值,在官方文档上都没有找到说明,还有ProviderConfig
和ConsumerConfig
,这两个对应的标签是<dubbo:provider>
和<dubbo:consumer>
,但是我们现在都是用<dubbo:service>
来暴露服务,用<dubbo:reference>
来引用服务。这里让我不解,望大佬们指教。
第三部分 关键属性解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { Set<String> props = new HashSet<>(); ManagedMap parameters = null ; for (Method setter : beanClass.getMethods()) { String name = setter.getName(); if (name.length() > 3 && name.startsWith("set" ) && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1 ) { Class<?> type = setter.getParameterTypes()[0 ]; String beanProperty = name.substring(3 , 4 ).toLowerCase() + name.substring(4 ); String property = StringUtils.camelToSplitName(beanProperty, "-" ); props.add(property); Method getter = null ; try { getter = beanClass.getMethod("get" + name.substring(3 ), new Class<?>[0 ]); } catch (NoSuchMethodException e) { try { getter = beanClass.getMethod("is" + name.substring(3 ), new Class<?>[0 ]); } catch (NoSuchMethodException e2) { } } if (getter == null || !Modifier.isPublic(getter.getModifiers()) || !type.equals(getter.getReturnType())) { continue ; } if ("parameters" .equals(property)) { parameters = parseParameters(element.getChildNodes(), beanDefinition); } else if ("methods" .equals(property)) { parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); } else if ("arguments" .equals(property)) { parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); } else { String value = element.getAttribute(property); if (value != null ) { value = value.trim(); if (value.length() > 0 ) { if ("registry" .equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(RegistryConfig.NO_AVAILABLE); beanDefinition.getPropertyValues().addPropertyValue(beanProperty, registryConfig); } else if ("provider" .equals(property) || "registry" .equals(property) || ("protocol" .equals(property) && ServiceBean.class .equals (beanClass ))) { beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids" , value); } else { Object reference; if (isPrimitive(type)) { if ("async" .equals(property) && "false" .equals(value) || "timeout" .equals(property) && "0" .equals(value) || "delay" .equals(property) && "0" .equals(value) || "version" .equals(property) && "0.0.0" .equals(value) || "stat" .equals(property) && "-1" .equals(value) || "reliable" .equals(property) && "false" .equals(value)) { value = null ; } reference = value; } else if ("onreturn" .equals(property)) { int index = value.lastIndexOf("." ); String returnRef = value.substring(0 , index); String returnMethod = value.substring(index + 1 ); reference = new RuntimeBeanReference(returnRef); beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod" , returnMethod); } else if ("onthrow" .equals(property)) { int index = value.lastIndexOf("." ); String throwRef = value.substring(0 , index); String throwMethod = value.substring(index + 1 ); reference = new RuntimeBeanReference(throwRef); beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod" , throwMethod); } else if ("oninvoke" .equals(property)) { int index = value.lastIndexOf("." ); String invokeRef = value.substring(0 , index); String invokeRefMethod = value.substring(index + 1 ); reference = new RuntimeBeanReference(invokeRef); beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod" , invokeRefMethod); } else { if ("ref" .equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) { BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value); if (!refBean.isSingleton()) { throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>" ); } } reference = new RuntimeBeanReference(value); } beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference); } } } } } } }
从这部分看,解析xml到实体的套路我们已经知道了。他是通过set方法把xml属性设置到实体中。然后放到对应的beanDefinition中。
第四部分 其他属性解析
这也是该方法的最后一部分,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { NamedNodeMap attributes = element.getAttributes(); int len = attributes.getLength(); for (int i = 0 ; i < len; i++) { Node node = attributes.item(i); String name = node.getLocalName(); if (!props.contains(name)) { if (parameters == null ) { parameters = new ManagedMap(); } String value = node.getNodeValue(); parameters.put(name, new TypedStringValue(value, String.class )) ; } } if (parameters != null ) { beanDefinition.getPropertyValues().addPropertyValue("parameters" , parameters); } return beanDefinition; }
上面这部分代码就是把其他属性,作为一个parameters放到beanDefinition中。到此,整个parse(Element,ParserContext,Class<?>,boolean)
也就在我们的猜想中静态分析完了,其中有些地方不是很明确,也可能有些地方有错误,还望各位大佬能够不吝指教一番。
下面就让我们用动态调试的方式来验证我们的猜想吧。
动态调试Dubbo配置解析流程
动态调试,在哪下断点是关键,既然我们说最后无论如何都会到我们的DubboBeanDefinitionParser#parse(Element,ParserContext,Class<?>,boolean)
方法,那我们就在首行下个断点,让应用跑起来。
现在我们通过断点调试来确定下下面的问题。
是否可以在我们加的断点中断下来。
parserContext.getRegistry()
返回的具体实现类是啥?
最后解析的结果去哪了?
第一个问题 是否可以断下来
答案是肯定的,如图:
第二个问题 parserContext.getRegistry()
返回的实现类是什么?
我们来到该方法调用处,截图如下:
看上面的图来说,具体的实现类是DefaultListableBeanFactory
,既然知道了是这个类,那么有地方就得说说了。我们继续回头看看这段代码,这段代码在第一部分 准备
这一节里面
1 2 3 4 5 6 7 8 9 if (id != null && id.length() > 0 ) { if (parserContext.getRegistry().containsBeanDefinition(id)) { throw new IllegalStateException("Duplicate spring bean id " + id); } parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); beanDefinition.getPropertyValues().addPropertyValue("id" , id); }
DefaultListableBeanFactory#registerBeanDefinition(String, BeanDefinition)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Override public void registerBeanDefinition (String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty" ); Assert.notNull(beanDefinition, "BeanDefinition must not be null" ); if (beanDefinition instanceof AbstractBeanDefinition) { } BeanDefinition oldBeanDefinition; oldBeanDefinition = this .beanDefinitionMap.get(beanName); if (oldBeanDefinition != null ) { else { if (hasBeanCreationStarted()) { }else { this .beanDefinitionMap.put(beanName, beanDefinition); this .beanDefinitionNames.add(beanName); this .manualSingletonNames.remove(beanName); } this .frozenBeanDefinitionNames = null ; } if (oldBeanDefinition != null || containsSingleton(beanName)) { resetBeanDefinition(beanName); } }
所谓的registerBeanDefinition
就是在把bean
保存在DefaultListableBeanFactory
的一个成员属性中,也就是一个Map中。
那么我们看一下parserContext.getRegistry().containsBeanDefinition(id)
DefaultListableBeanFactory#containsBeanDefinition(String)
1 2 3 4 5 @Override public boolean containsBeanDefinition (String beanName) { Assert.notNull(beanName, "Bean name must not be null" ); return this .beanDefinitionMap.containsKey(beanName); }
上面的代码清晰可见,当我们调用该方法时,会去看看Map中是否有该key。
最后一个问题,解析的配置存哪了?
没错,最后的解析后的配置就放在了我们的DefaultListableBeanFactory
的一个Map成员属性中,也就是beanDefinitionMap
总结
这一篇篇幅较上一篇长很多,大致说了如下几个事
Dubbo中配置实体的结构
Dubbo的配置是如何解析的,也就是解析流程。
动态调试,验证猜想和确定了parserContext.getRegistry()
的具体实现类
好了,这一篇就到这里了,有错误还望大佬们不吝指教,最后,五一劳动节快乐~咱们啊王者峡谷见~