Dubbo之DubboBeanDefinitionParser源码解析

news/2024/12/5 9:28:58/

功能概述

  • Dubbo框架会集成Spring的能力,在以XML方式配置信息时,会用自定义的处理器DubboNamespaceHandler和解析器DubboBeanDefinitionParser对XML进行解析,生成对应的Bean对象和Config对象。

功能分析

DubboBeanDefinitionParser类分析

主要成员变量分析

private final Class<?> beanClass; // Bean对应的Class类(即Config的Class类)
private final boolean required;   // 是否必须(即Config类的id是否必须)

主要成员方法分析

解析XML元素

private static RootBeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { //ParserContext:通过bean定义解析过程传递的上下文,封装所有相关配置和状态,嵌套在XmlReaderContext内RootBeanDefinition beanDefinition = new RootBeanDefinition(); //创建spring的Bean实例beanDefinition.setBeanClass(beanClass); //设置Bean对应的class类,如MethodConfig.classbeanDefinition.setLazyInit(false); //设置是否延迟初始化,false:在spring容器启动时,就会创建Bean实例String id = resolveAttribute(element, "id", parserContext); //解析属性id的值/*** 处理属性id的值,若属性id为空且是必须的,则尝试获取name、interface属性对应的值,若还为空则获取bean的名称*/if (StringUtils.isEmpty(id) && required) { //未设置属性id时,使用其它属性产生id值String generatedBeanName = resolveAttribute(element, "name", parserContext); //解析属性name的值if (StringUtils.isEmpty(generatedBeanName)) {if (ProtocolConfig.class.equals(beanClass)) {generatedBeanName = "dubbo";} else {generatedBeanName = resolveAttribute(element, "interface", parserContext);}}if (StringUtils.isEmpty(generatedBeanName)) {generatedBeanName = beanClass.getName(); //bean的名称如:class org.apache.dubbo.config.ApplicationConfig}id = generatedBeanName;int counter = 2;while (parserContext.getRegistry().containsBeanDefinition(id)) { //若bean已存在,则加上计数标识,直到没有重复的id为止id = generatedBeanName + (counter++); // count++的值从2开始递增,变量count在前,count++就是加之前的值}}if (StringUtils.isNotEmpty(id)) {if (parserContext.getRegistry().containsBeanDefinition(id)) { //判断已经注册的Bean中是否有重复的idthrow new IllegalStateException("Duplicate spring bean id " + id);}parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); // 向spring注册中心注册bean实例(将id作为Bean的名称)beanDefinition.getPropertyValues().addPropertyValue("id", id); // 设置bean的id属性}/*** 对指定的bean进行处理,如ProtocolConfig、ServiceBean、ProviderConfig、ConsumerConfig等*/if (ProtocolConfig.class.equals(beanClass)) {for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { //遍历已注册的bean对应的名称列表BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); //获取指定bean名称对应的BeanDefinition实例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 = resolveAttribute(element, "class", parserContext);if (StringUtils.isNotEmpty(className)) { //在<dubbo:service/>中设置class属性时进入RootBeanDefinition classDefinition = new RootBeanDefinition(); //根bean定义也可以用于注册单个bean定义classDefinition.setBeanClass(ReflectUtils.forName(className));classDefinition.setLazyInit(false);parseProperties(element.getChildNodes(), classDefinition, parserContext);beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); //BeanDefinitionHolder:带有名称和别名的bean定义的Holder// 此处的bean实例,会在bean的名称后面加上Impl,如:org.apache.dubbo.demo.DemoService + "Impl",对应的实现类是org.apache.dubbo.demo.DemoServiceImpl}} else if (ProviderConfig.class.equals(beanClass)) { //对<dubbo:provider> 中的嵌套<dubbo:service> 元素进行解析parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);} else if (ConsumerConfig.class.equals(beanClass)) { //对<dubbo:consumer> 中的嵌套<dubbo:reference> 元素进行解析parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);}Set<String> props = new HashSet<>();ManagedMap parameters = null; //托管的Map(Spring的标签集合类,用于保存被托管的Map)for (Method setter : beanClass.getMethods()) { //遍历Config中的set方法提取出属性名,然后通过XML的Element解析出对应的属性值,最后依次设置到Config类关联的Bean的属性中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); //解析出属性名,如方法名为setName,属性名为nameString property = StringUtils.camelToSplitName(beanProperty, "-"); //按分隔符方式处理属性名称props.add(property); //将属性加到属性集合中// check the setter/getter whether match (检查set、get方法是否匹配)Method getter = null;try {getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);//获取指定的属性对应的get方法,如getName} catch (NoSuchMethodException e) {try {getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]); //处理is开头的方法,比如ApplicationConfig中的isDefault()方法} catch (NoSuchMethodException e2) { //允许没有get方法,比如EnvironmentAware,只有set方法,不能抛出异常,不然引起应用启动失败// ignore, there is no need any log here since some class implement the interface: EnvironmentAware,// ApplicationAware, etc. They only have setter method, otherwise will cause the error log during application start up.}}if (getter == null|| !Modifier.isPublic(getter.getModifiers())|| !type.equals(getter.getReturnType())) { //若没有找到符合条件的get方法,则本次不处理,跳到下次循环continue;}/*** 从XML元素中读取Config类属性对应的值,并设置到Bean的属性中*/if ("parameters".equals(property)) {parameters = parseParameters(element.getChildNodes(), beanDefinition, parserContext); //解析<dubbo:parameter/>元素的值} else if ("methods".equals(property)) {parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); //解析<dubbo:method/>元素的值} else if ("arguments".equals(property)) {parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); //解析<dubbo:argument/>元素的值} else { //对常规属性做处理String value = resolveAttribute(element, property, parserContext); //解析XML中元素对应的属性值if (value != null) { //若属性值为不为空,则对应处理(即XML中设置了对应的属性值)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) && AbstractServiceConfig.class.isAssignableFrom(beanClass))) {// Class中方法isAssignableFrom():判断当前class是否与参数中指定的class相同,或者是参数指定的class的父类或父接口(主语是当前class,如上的主语即为:AbstractServiceConfig)beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);} else {Object reference;if (isPrimitive(type)) { //对基本类型的属性处理(基本类型进行了扩展,包含String、Date等)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)) {// backward compatibility for the default value in old version's xsd(向后兼容旧版本的XSD中的默认值)value = null; //若旧版本的属性满足条件,则将属性值设置为null}reference = value;// 对方法<dubbo:method>元素中的onreturn、onthrow、oninvoke属性进行处理} else if (ONRETURN.equals(property) || ONTHROW.equals(property) || ONINVOKE.equals(property)) {int index = value.lastIndexOf("."); //待覆盖调试:事件通知允许Consumer端在调用之前、调用之后或出现异常时,触发oninvoke、onreturn、onthrow三个事件。 https://dubbo.apache.org/zh/docs/advanced/events-notify/String ref = value.substring(0, index); //onreturn、onthrow、oninvoke的属性值,必须要以 xxx.方法名形式,若没有".",则会由于[0,-1)报字符串区间错误 "String index out of range: -1"String method = value.substring(index + 1);reference = new RuntimeBeanReference(ref);beanDefinition.getPropertyValues().addPropertyValue(property + METHOD, method);} else { //解析ref属性<property name="ref">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); //创建指定名称的bean}beanDefinition.getPropertyValues().addPropertyValue(beanProperty, reference); //为bean添加属性值}}}}}}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的parameters属性值beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);}return beanDefinition;
}
  • 代码解析:
    • 代码中的RootBeanDefinition时Spring的Bean,可以作为其他BeanDefinition的父BeanDefinition,也可以单独作为BeanDefinition,但是不能作为其他BeanDefinition的子BeanDefinition。
    • 代码中的NamedNodeMap代表一个节点的无序列表,可以通过它们的名称进行访问。

解析内嵌元素

private static void parseNested(Element element, ParserContext parserContext, Class<?> beanClass, boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) {NodeList nodeList = element.getChildNodes(); //获取子节点列表if (nodeList == null) {return;}boolean first = true;for (int i = 0; i < nodeList.getLength(); i++) { //可以有多个内嵌节点Node node = nodeList.item(i);if (!(node instanceof Element)) {continue;}if (tag.equals(node.getNodeName()) //带上命名空间的节点名称,如dubbo:service(按指定的tag查找Node)|| tag.equals(node.getLocalName())) { //去掉命名空间的名称,如serviceif (first) { //有多个元素时,如<dubbo:service>,只有第一个元素才处理default值first = false; //处理好第一个元素后,更改该标志String isDefault = resolveAttribute(element, "default", parserContext); // default: 是否为缺省协议,用于多协议if (StringUtils.isEmpty(isDefault)) { //处理默认属性defaultbeanDefinition.getPropertyValues().addPropertyValue("default", "false");}}// 内部嵌套的元素,按单个元素解析的方式依次解析BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required); //获取嵌套元素对应的Bean,如<dubbo:provider>中<dubbo:service>if (subDefinition != null && StringUtils.isNotEmpty(ref)) { //依赖的bean用RuntimeBeanReference表示subDefinition.getPropertyValues().addPropertyValue(property, new RuntimeBeanReference(ref));}}}
}
  • 代码解析:
    • BeanDefinition:在Spring中,Bean的解析阶段,会把xml配制中的标签解析成Spring中的BeanDefinition对象
    • RuntimeBeanReference:如果一个bean依赖其它的bean,比如dubbo:service中ref,那么被依赖的bean就用RuntimeBeanReference表示(因为解析阶段,还没有依赖的bean的实例,等解析以后存在实例时,再根据RuntimeBeanReference关联)

解析元素属性值

private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition, ParserContext parserContext) {if (nodeList == null) {return;}for (int i = 0; i < nodeList.getLength(); i++) { //NodeList: 提供了有序节点的抽象节点的集合if (!(nodeList.item(i) instanceof Element)) {continue;}Element element = (Element) nodeList.item(i);if ("property".equals(element.getNodeName()) //只对属性节点处理<property>|| "property".equals(element.getLocalName())) {String name = resolveAttribute(element, "name", parserContext); //解析<property>中的name属性if (StringUtils.isNotEmpty(name)) {String value = resolveAttribute(element, "value", parserContext);String ref = resolveAttribute(element, "ref", parserContext);if (StringUtils.isNotEmpty(value)) { //解析基本属性beanDefinition.getPropertyValues().addPropertyValue(name, value); //属性值列表beanDefinition.getPropertyValues(), 对应<property>的值} else if (StringUtils.isNotEmpty(ref)) { //解析含有引用的属性,创建对应的beanbeanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref));} else {throw new UnsupportedOperationException("Unsupported <property name=\"" + name + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\"" + name + "\" value=\"...\" />");}}}}
}

解析参数标签

private static ManagedMap parseParameters(NodeList nodeList, RootBeanDefinition beanDefinition, ParserContext parserContext) {if (nodeList == null) { //NodeList表示一个有顺序的节点列表return null;}ManagedMap parameters = null; //托管的Map,用来保存map的值for (int i = 0; i < nodeList.getLength(); i++) { //可以有多个元素,如:<dubbo:parameter>if (!(nodeList.item(i) instanceof Element)) { //若元素不是Element实例,则不处理continue;}Element element = (Element) nodeList.item(i);if ("parameter".equals(element.getNodeName())|| "parameter".equals(element.getLocalName())) { //只处理<dubbo:parameter/>if (parameters == null) {parameters = new ManagedMap();}String key = resolveAttribute(element, "key", parserContext); //解析<dubbo:parameter> 元素中的keyString value = resolveAttribute(element, "value", parserContext); //解析<dubbo:parameter> 元素中的valueboolean hide = "true".equals(resolveAttribute(element, "hide", parserContext));if (hide) { //是否隐藏,若需要隐藏,加上前缀key = HIDE_KEY_PREFIX + key;}parameters.put(key, new TypedStringValue(value, String.class)); //多个 <dubbo:parameter>时,若key重复,value值会被覆盖(TypedStringValue:存储了string值,已经要转换的目标类型,类型转换由bean工厂处理)}}return parameters; //此处没有直接添加到bean的属性中,而是在DubboBeanDefinitionParser#parse方法的最后添加的
}

DubboNamespaceHandler类分析

主要成员方法分析

初始化方法

public void init() { //在解析自定义元素前,进行初始化操作(设置父类NamespaceHandlerSupport的Map<String, BeanDefinitionParser> parsers,即设置元素名与bean解析器的映射)registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.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)); //将元素名与对应的bean进行对应registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); //对应注解解析器
}
  • 代码解析:
    • init()方法解析:
      • 重写Spring NamespaceHandler的init()方法,
      • 在解析XML中的命名空间url时,如xmlns:dubbo="http://dubbo.apache.org/schema/dubbo",会调用init()方法,
      • 调用的地方org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
    • registerBeanDefinitionParser()方法解析:
      • 重写Spring NamespaceHandlerSupport#registerBeanDefinitionParser()方法
      • 将元素名,如"application"与对应的解析器按键值对存储起来 Map<String, BeanDefinitionParser> parsers
      • 注册以后当前对象DubboNamespaceHandler从NamespaceHandlerSupport继承的私有成员变量parsers就有相关值了

问题点答疑

  • DubboBeanUtils#registerCommonBeans功能用途是什么?

    • 解答:用来注册含有基本功能的bean,如DubboBootstrapApplicationListener、ReferenceAnnotationBeanPostProcessor等等
      会打印出"The Infrastructure bean definition"日志
  • 是怎样加载到自定义的DubboNamespaceHandler的?

    • 解答:当Spring读取XML时,遇到非Spring定义XML元素时,就会查找spring.schemas和spring.handlers,在spring.handlers中配置了DubboNamespaceHandler处理器。

归纳总结

  • XML关联信息介绍

    • XML指可扩展标记语言、被设计用来传输和存储数据。HTML被设计用来显示数据
    • 第一行是XML声明,XML文档必须包含根元素(有且只有一个根元素)。该元素是所有其他元素的父元素。XML文档形成一种树结构。
    • XML元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。
    • 拥有正确语法的XML被称为“形式良好”的XML。可以通过DTD、Schema验证的XML是否是“合法”的XML。
    • 解析器把XML转换为XML DOM对象,XML DOM (XML Document Object Model) 定义了访问和操作XML文档的标准方法。
    • XML命名空间:提供避免元素命名冲突的方法。所有 XML 文档中的文本均会被解析器解析。只有 CDATA 区段(CDATA section)中的文本会被解析器忽略
  • DOM中的节点Node介绍:

    • XML 文档中的每个成分都是一个节点。
    • DOM 对应的规定:
      • 整个文档是一个文档节点
      • 每个 XML 标签是一个元素节点
      • 包含在 XML 元素中的文本是文本节点
      • 每一个 XML 属性是一个属性节点
      • 注释属于注释节点

http://www.ppmy.cn/news/1040972.html

相关文章

7zip分卷压缩

前言 有些项目上传文件大小有限制 压缩包大了之后传输也会比较慢 解决方案 我们可以利用7zip压缩工具对文件进行分卷压缩 利用7zip压缩工具进行分卷压缩 查看待压缩文件大小 压缩完成之后有300多M&#xff0c;我们用100M去进行分卷压缩 选择待压缩的文件夹&#xff0c;右…

代码随想录训练营day17|110.平衡二叉树 257. 二叉树的所有路径 404.左叶子之和 v...

TOC 前言 代码随想录算法训练营day17 一、Leetcode 110.平衡二叉树 1.题目 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 示例 1&#x…

解决Adobe Flash Player已被屏蔽

问题&#xff1a;该插件不支持 原因&#xff1a;现在浏览器默认禁用flash 博主当前使用的是谷歌浏览器Chrome 2个主要方法都已经失效 搜索一圈后&#xff0c;之前博客给出的2个主要方法都已经失效。 1、flash.cn 下载本地播放器 2、在chrome中打开flash的禁用开关 2023年解…

ZLMediakit-集群部署

集群部署 痛点&#xff1a; 溯源方式单一&#xff0c;边沿服务器不能使用HLS。 场景介绍: 一般流媒体集群实现方式采用溯源方式实现&#xff0c;服务器分为源站和边沿站。源站一般用于接收推流&#xff0c;它一般不直接承载用户的播放请求&#xff0c;而是通过边沿服务器向其拉…

Redis——list类型详解

概要 Redis中的list类型相当于双端队列&#xff0c;支持头插&#xff0c;头删&#xff0c;尾插&#xff0c;尾删&#xff0c;并且列表中的内容是可以重复的。 如果搭配使用rpush和lpop&#xff0c;那么就相当于队列 如果搭配使用rpush和rpop&#xff0c;那么就相当于栈 lpu…

低代码平台全套源码,支持二次开发

低代码开发平台&#xff1a;只需要编写简单的配置文件即可构建企业级应用程序。 一、低代码PaaS平台可以在云端开发、部署、运行低代码应用程序。使用独立数据库模型&#xff0c;基于Kubernetes云原生技术&#xff0c;每个租户均可拥有一套独立的存储、数据库、代码和命名空间&…

ardupilot开发 --- log篇

懂的都懂&#xff0c;你也要懂 log作用记录您的飞行数据&#xff1b; 两种方式或类型&#xff1a; Data flash log &#xff0c;通常记录在SD卡上&#xff0c;可通过地面站下载&#xff1b; Telemetry logs&#xff08;Tlogs&#xff09;&#xff0c;地面站通过无线设备进行实…

算法修炼Day52|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组

LeetCode:300.最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 1.思路 dp[i]的状态表示以nums[i]为结尾的最长递增子序列的个数。 dp[i]有很多个&#xff0c;选择其中最大的dp[i]Math.max(dp[j]1,dp[i]) 2.代码实现 1class Solution {2 pub…