前言

SOFA-Boot 现阶段支持 XML 的方式在 Spring 中定义 Bean,通过这些标签,我们就能从 Spring 容器中取出 RPC 中的引用,并进行调用,那么他是如何处理这些自定义标签的呢?一起来看看。

如何使用?

官方例子:

<?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:sofa="http://sofastack.io/schema/sofaboot"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://sofastack.io/schema/sofaboot http://sofastack.io/schema/sofaboot.xsd"
default-autowire="byName"> <bean id="personServiceImpl" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/> <sofa:service ref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
<sofa:binding.bolt/>
<sofa:binding.rest/>
</sofa:service> <sofa:reference id="personReferenceBolt" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
<sofa:binding.bolt/>
</sofa:reference> <sofa:reference id="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
<sofa:binding.rest/>
</sofa:reference> <bean id="personFilter" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceFilter"/> </beans>

显眼的 sofa 标签。那么如何知道他是怎么处理这些标签的内容的呢?

答:从上面的 xmlns 命名空间可以找到。如果写过自定义标签的话,一定很熟悉了。

如果没有写过的话,就简单介绍一下。Spring 支持用户自定标签,需要遵守以下规范。

  1. 编写 xsd 文件,就是定义标签的属性。

  2. 继承抽象类 NamespaceHandlerSupport 并实现 init 方法,此类就是处理命名空间的类,不仅需要实现 init 方法,你还需要继承 AbstractSingleBeanDefinitionParser 类,自行解析标签。通常写法是这样:

    registerBeanDefinitionParser("tagName",new UserBeanDefinitionParser());

  3. 在做完上面的步骤后,你需要在 META-INF 目录下编写 spring.handlersspring.schemas 文件,前者 key 为命名空间名称,value 为 NamespaceHandlerSupport 的具体实现;后者 key 为 xsd 命名空间,value 为 xsd 具体文件(classpath 下)。

完成上面的步骤后,Spring 在加载 xml 配置文件的时候,会检查命名空间,如果是自定义的,则会根据命名空间的 key 找到对应的解析器,也就是 NamespaceHandlerSupport 对自定义标签进行解析。

So,我们的目的是看 sofa 标签是如何解析的,则找到解析 sofa 的NamespaceHandlerSupport

寻找解析 sofa 标签的源头

通过 IDEA 或者别的工具全局搜索,我们找到了源码中位于 start 模块下,resources 下的 META-INF 目录,目录下有以下几个文件:

  1. rpc.xsd
  2. sofaboot.xsd
  3. spring.handlers
  4. spring.schemas

这几个文件我们是比较关注的,重点看 handlers 文件。内容如下:

http\://sofastack.io/schema/sofaboot=com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler

SofaBootNamespaceHandler 明显就是明明空间处理类,继承了 Spring 的 NamespaceHandlerSupport

该类的 init 方法通过 Java 的 SPI 进行扩展,找到 SofaBootTagNameSupport 的标签支持类,目前有 2 个实现: ServiceDefinitionParser 和 ReferenceDefinitionParser。

两个类支持不同的 element。一个是引用服务,一个是发布服务。

这样就比较清晰了。在得到两个类之后,注册到 Spring 的 parsers map 中。key 是 element 名字,value 是解析器。

具体的解析上面说了,service 和 reference。

Sofa 在这个设计上使用了模板模式,使用一个抽象类 AbstractContractDefinitionParser,并定义一个 doParseInternal 抽象方法让子类去实现。

抽象父类将一些公用的属性进行解析,下图中都是公用的属性:

你咋确定是公用的属性呢?首先,xml 解析后,肯定是要注入到 Bean 里面去的,那么这个 Bean 是什么呢?实际上,在 AbstractSingleBeanDefinitionParser 抽象类中,会有一个返回 Bean 类型的方法,对应着一个 tag,而他们的父类就是 AbstractContractFactoryBean

该类公用属性就对应上面图片中的定义。

有了这些基础的信息,还是不能够直接使用的,毕竟都是字符串。我们要看看 SOFA 是如何将自己和 Spring 容器融合在一起的。

如何融入 Spring?

来看看他们的子类, ReferenceDefinitionParser 类(ServiceFactoryBean 类似)。对应的 Bean 是 ReferenceFactoryBean,该类单独定义了负载均衡(loadBalance)的属性。

啊,从这个类的名字看,很熟悉,之前在分析 Spring 源码的时候,就看见过 FactoryBean。可以通过 getObject 方法修改返回的 Bean。

来看看这个类的 类图

如我们所料,继承了 Spring 中关键的扩展点。而他的 getObject 方法将返回一个代理:

   @Override
public Object getObject() throws Exception {
return proxy;
}

具体创建代理的方法是 com.alipay.sofa.runtime.service.component.ReferenceComponent 类的 createProxy 方法,然后调用适配器。适配器的作用就是胶水的作用,将 Spring 容器和第三方框架粘合起来,Spring 提供的接口则是 FactoryBean 等接口,而第三方框架可以在 getObject 方法中大有作为。

SOFA-Boot 的适配器在 om.alipay.sofa.rpc.boot.runtime.adapter 包中,具体实现则是 RpcBindingAdapter 类,其中 outBinding 方法用于发布服务,inBinding 方法用于引用服务,这里直接使用的就是 SOFA-RPC 的 consumerConfig 和 providerConfig。这就应该很熟悉了吧。哈哈。

来点代码看看(已去除异常处理):

@Override
public Object outBinding(Object contract, RpcBinding binding, Object target, SofaRuntimeContext sofaRuntimeContext) { String uniqueName = ProviderConfigContainer.createUniqueName((Contract) contract, binding);
ProviderConfig providerConfig = ProviderConfigContainer.getProviderConfig(uniqueName); providerConfig.export(); if (ProviderConfigContainer.isAllowPublish()) {
Registry registry = RegistryConfigContainer.getRegistry();
providerConfig.setRegister(true);
registry.register(providerConfig);
}
return Boolean.TRUE;
}
@Override
public Object inBinding(Object contract, RpcBinding binding, SofaRuntimeContext sofaRuntimeContext) {
ConsumerConfig consumerConfig = ConsumerConfigHelper.getConsumerConfig((Contract) contract, binding);
ConsumerConfigContainer.addConsumerConfig(binding, consumerConfig); Object result = consumerConfig.refer();
binding.setConsumerConfig(consumerConfig);
return result;
}

So,当 Spring 容器使用 getObject 方法的时候,获取的就是这个代理对象啦。是不是很简单?

有一点需要注意一下,ServiceFactoryBean 的设计和 ReferenceFactoryBean 确实是类似,但是!但是!他的 getObject 方法就是实现类本身,这是 RPC 框架本身的设计决定的,因为它不需要代理,只需要发布服务就行,当收到了客户端传来的信息,就直接调用实现类的指定方法就好了,没有客户端这么复杂。

当然,SOFA 中还有一个组件的概念,我们有时间会好好看看这块的设计。

总结

这次我们从 SOFA-Boot 配置文件的 xml 标签开始,对他如何融入 Spring 进行了分析,实际上,整体和我们联想的类似,使用 Spring 的 FactoryBean 的 getObject 方法返回代理,如果是客户端的话,在 getObject 方法中,会创建一个动态代理,这就要使用 SOFA-RPC 了,所以,融合 RPC 和 Spring 的任务肯定是个适配器。SOFA 的实现就是 RpcBindingAdapter 类。在该类中,将 RPC 和 Spring 适配。

而发布服务相比较引用服务就简单一点了,整体上就是通过注解将服务发布,当然也是使用的 RpcBindingAdapter 进行适配。但是没有使用代理(不需要)。

好了,这次研究 SOFA 和 Spring 融合的过程就到这里啦。

bye !

从 <sofa:XXX> 标签开始看 SOFA-Boot 如何融入 Spring的更多相关文章

  1. 【spring boot 系列】spring data jpa 全面解析(实践 + 源码分析)

    前言 本文将从示例.原理.应用3个方面介绍spring data jpa. 以下分析基于spring boot 2.0 + spring 5.0.4版本源码 概述 JPA是什么? JPA (Java ...

  2. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件

    精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件 内容简介:本文介绍 Spring Boot 的配置文件和配置管理,以及介绍了三种读取配置文 ...

  3. spring boot(五):spring data jpa的使用

    在上篇文章springboot(二):web综合开发中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jpa 常见用法以及注意事项 使用spr ...

  4. 使用 Spring Boot 快速构建 Spring 框架应用--转

    原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-spring-boot/ Spring 框架对于很多 Java 开发人员来说都不陌生.自从 2 ...

  5. 使用 Spring Boot 快速构建 Spring 框架应用,PropertyPlaceholderConfigurer

    Spring 框架对于很多 Java 开发人员来说都不陌生.自从 2002 年发布以来,Spring 框架已经成为企业应用开发领域非常流行的基础框架.有大量的企业应用基于 Spring 框架来开发.S ...

  6. Spring Boot——开发新一代Spring应用

    Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...

  7. Spring Boot中使用 Spring Security 构建权限系统

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,为应用系统提供声明式的安全 ...

  8. Spring Boot(五):Spring Boot Jpa 的使用

    在上篇文章Spring Boot(二):Web 综合开发中简单介绍了一下 Spring Boot Jpa 的基础性使用,这篇文章将更加全面的介绍 Spring Boot Jpa 常见用法以及注意事项. ...

  9. (转)Spring Boot(五):Spring Boot Jpa 的使用

    http://www.ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html 在上篇文章Spring Boot(二):Web 综合开发中简单介 ...

随机推荐

  1. UITableView大总结(上)

    1.UITableView继承UIScrollView. 2.实例一城市列表思路: 步骤一:创建UITableView.UITableView样式为组 步骤二:设置UITableView的数据源方法. ...

  2. 多线程爬虫Miner

    多线程爬虫Miner 需要配置项:1.URL包含关键字.2.存储方式:DB-数据库存储;FILE-文件存储.3.爬取页面最大深度.4.下载页面线程数.5.分析页面线程数.6.存储线程数. ------ ...

  3. javascript语法之number对象和Math对象

    这两个对象很简单,一个例子就能掌握用法. 一:number对象. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional// ...

  4. JAVA之旅(十一)——RuntimeException,异常的总结,Package,jar包,多线程概述

    JAVA之旅(十一)--RuntimeException,异常的总结,Package,jar包,多程序概述 继续JAVA之旅 一.RuntimeException 在Exception种有一个特殊的子 ...

  5. 学习pthreads,使用属性对象创建结合线程和分离线程

    当我们创建了子线程,是让它犹如脱缰之马,信步驰骋,还是如乖巧听话的孩子,时不时教导一下呢?针对这个问题,本文介绍线程的结合和分离,结构分为三个部分,第一部分给出代码示例,第二部分对代码进行讲解,第三部 ...

  6. 网站开发进阶(三十二)HTML5之FileReader的使用

    HTML5之FileReader的使用 HTML5定义了FileReader作为文件API的重要成员用于读取文件,根据W3C的定义,FileReader接口提供了读取文件的方法和包含读取结果的事件模型 ...

  7. mysql进阶(三)游标简易教程

    mysql游标简易教程 从mysql V5.5开始,进行了一次大的改变,就是将InnoDB作为默认的存储引擎.InnoDB支持事务,而且拥有相关的RDBMS特性:ACID事务支持,数据完整性(支持外键 ...

  8. Dynamics CRM2013/2015 禁止欢迎界面(Disable the Welcome Screen)

    首次打开Dynamic CRM  2013会出现一个欢迎界面如下图,要想它不出现勾选图中的复选框就行,OK下回再打开就没有了. 但是当我们打开F12开发人员工具,清除域的缓存后再次打开CRM,这个欢迎 ...

  9. Mahout朴素贝叶斯文本分类

    Mahout朴素贝叶斯文本分类算法 Mahout贝叶斯分类器按照官方的说法,是按照<Tackling the PoorAssumptions of Naive Bayes Text Classi ...

  10. python函数参数是值传递还是引用传递(以及变量间复制后是否保持一致):取决于对象内容可变不可变

    函数参数传递本质上和变量整体复制一样,只是两个变量分别为形参a和实参b.那么,a=b后,a变了,b值是否跟着变呢?这取决于对象内容可变不可变 首先解释一下,什么是python对象的内容可变不可变? p ...