soul网关-5-普通springboot项目接入soul网关的原理

如果有多个产品线售卖API,那么他们都会有一些共性的需求,例如签名认证、限速等,为了避免每个产品线重复开发,那么可以实现一个网关系统来统一提供这些功能。

那么大致的流程是这样的:

  • 1.产品线将自己要售卖的API注册到网关管理后台
  • 2.网关管理后台将数据同步到网关系统
  • 3.用户购买了某产品线的API之后,调用API。请求是统一发往网关的。
  • 4.网关对用户请求进行代理,转发给相应的产品线

前几篇笔记里面,学习了soul网关divide插件如何进行请求代理和数据同步机制之websocket,对上述步骤里面的2和4有了大概的了解。

在今天的源码学习之前,可以设想一下,假如让我来为各产品线提供一个注册API到网关管理后台的方案的话,可能是这样的:

  • 网关管理后台除了给管理员提供的用于UI界面CRUD的接口之外,还提供一套批量接口,这套批量接口都需要token进行鉴权,token是分发给每个产品线的
  • 产品线使用分配到的token,调用相应的批量接口(批量创建、批量删除、批量更新)等,将自己的API注册到网关管理后台

这个方案的缺点是,有一定的学习成本,提供的接口放在一个文档里面,各个产品线的RD需要自行阅读和学习;接入时间长,从开发到调试到正式接入,至少要两天的时间;对产品线的业务或者业务后台有比较大的侵入性,总要在某个地方有一些代码是将API注册到网关管理后台的。总体来说就是不够无痛。

本文就来学习一下soul网关项目是如何简便地让产品线将要售卖的API注册到网关管理后台的。

首先看一下官方文档里面关于项目接入的介绍,就先看普通的springboot项目如何接入soul网关,很方便,只需要几个步骤就可以,分别是:

  • 在自己的springboot项目的pom里面,加入soul-spring-boot-starter-client-springmvc依赖
	
         org.dromara
         soul-spring-boot-starter-client-springmvc
         ${last.version}
     
  • 在springboot项目的application.yml里面加入一些配置信息
   soul:
     http:
       adminUrl: http://localhost:9095
       port: 你本项目的启动端口
       contextPath: /http
       appName: http
       full: false  
   # adminUrl: 为你启动的soul-admin 项目的ip + 端口,注意要加http://
   # port: 你本项目的启动端口
   # contextPath: 为你的这个mvc项目在soul网关的路由前缀,这个你应该懂意思把? 比如/order ,/product 等等,网关会根据你的这个前缀来进行路由.
   # appName:你的应用名称,不配置的话,会默认取 `spring.application.name` 的值
   # full: 设置true 代表代理你的整个服务,false表示代理你其中某几个controller
  • 在你的 controller 的接口上加上 @SoulSpringMvcClient 注解

    • 如果整个controller里面的所有接口都需要注册到网关,那么可以在controller类上面加上@SoulSpringMvcClient注解
    • 如果controller里面的一些接口而不是全部接口要注册到网关,那么可以在指定的接口上面加上@SoulSpringMvcClient注解
  • 启动你的springboot项目之后,你的接口就接入到了网关

    • 可以看下控制台的日志,确认是否已经成功接入网关。如果成功接入,会看到register success字样的日志

register success

整个过程感觉非常顺畅,从官方文档里面给出的信息可以知道,接入方式这么简便,最主要的是使用了@SoulSpringMvcClient这个soul自定义的注解。

那么就可以猜测大致的接入过程是这样的:

  • 因为你在你的springboot项目里添加了soul-spring-boot-starter-client-springmvc依赖。你的项目启动后,这个依赖会扫描你项目中有@SoulSpringMvcClient注解的controller以及接口
  • 对于根据你项目里面的代码声明@SoulSpringMvcClient注解的时候给这个注解传递的path值,可以知道你要注册到网关的接口
    • 例如,你的springboot项目里,这个接口是/hello,但是在这个接口上面的注解是@SoulSpringMvcClient(path = "/hi"),那么其实是把/hi注册到了网关
  • 再读取你的springboot项目的application.yml配置的信息,例如项目的appName、contextPath接口的前缀。可以生成完整的供外部用户向网关请求的url
    • 例如你的项目的contextPath是/myProject,你要注册的接口是/hello,网关的对外域名是xxxgateway.com。那么外部用户就通过请求http://xxxgateway.com/myProject/hello来访问你的/hello接口。
  • 通过application.yml里面配置的网关管理后台的url,向这个url发送请求,将上述接口信息注册到网关管理后台去

我们以@SoulSpringMvcClient为切入点来具体看一下soul网关的源码,看一下与我们猜测的是否符合,学习一下里面的细节。

看一下@SoulSpringMvcClient的定义,里面有path、ruleName等属性,是一个普通的注解

soul网关-5-普通springboot项目接入soul网关的原理_第1张图片

一般都是通过反射拿到的注解,全局搜索一下,看下哪里通过反射拿到了@SoulSpringMvcClient注解,全局搜SoulSpringMvcClient.class, 发现SpringMvcClientBeanPostProcessor这个类里面有相关代码。

public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {

    private final ThreadPoolExecutor executorService;

    private final String url;

    private final SoulSpringMvcConfig soulSpringMvcConfig;

    ……
    ……
    ……

SpringMvcClientBeanPostProcessor实现了spring框架的BeanPostProcessor接口,我们知道BeanPostProcessor接口里有两个方法,分别是postProcessBeforeInitializationpostProcessAfterInitialization,分别在 bean初始化方法调用前被调用,和 在bean初始化法调用后被调用

看下SpringMvcClientBeanPostProcessor对于上述两个方法的实现。发现它Override了postProcessAfterInitialization方法。里面的逻辑大致就是像我们上面猜测的那样:通过反射获取声明了@SoulSpringMvcClient注解的类和方法,将他们的信息注册到网关管理后台去,网关管理后台用来接收API注册的url是http://localhost:9095/soul-client/springmvc-register

soul网关-5-普通springboot项目接入soul网关的原理_第2张图片

这里有几个注意事项:

  • 如果application.yml配置文件中的soul.http.full是true,代表代理你的整个服务,false表示代理你其中某几个controller。如果full是false,则在SpringMvcClientBeanPostProcessorpostProcessAfterInitialization里面,会对每个bean都通过反射获取并判断它是否有@Controller@RestController@RequestMapping注解。
    • 即使在这里因为反射操作太多,导致你的项目启动时间长,我认为是可以接受的,因为毕竟springboot项目启动的时候本身就会进行一些池化之类的,启动时间本身就不短。而且是不影响你的项目的运行时间的。
  • 如果你在某个controller上面的@SoulSpringMvcClient注解是代表注册整个controller里面的所有接口的,例如HttpTestController上面的注解是@SoulSpringMvcClient(path = "/test/**"),它有n个接口,它只会在soul-admin管理后台生成一条相应的rule,并不会生成n条rule
  • 如果只想注册controller里面的某几个接口,那么它会通过反射获取签名不重复的方法(包括父类的方法)
    • final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
    • 方法的签名是由方法名称和一个参数列表(方法的参数的顺序和类型)组成
    • 它会判断取到的这些方法是否有@SoulSpringMvcClient注解
    • 如果controller的父类里面有public方法上有@SoulSpringMvcClient注解,那么该方法会被注册到网关管理后台
  • 如果controller里面需要注册到网关的接口里面,有两个接口的@SoulSpringMvcClient(path="xxx")注解配置了同样的path,虽然会调用两次网关管理后台用来接收API注册的url,但实际值生成一个rule。因为默认是以path为rule的name的,而rule的name是不能重复的

你的springboot项目注册API的时候,调用http://localhost:9095/soul-client/springmvc-register这个url,传的body是类似下方这样的数据:

{
    "appName":"http",
    "context":"/http",
    "path":"/http/order/findById",
    "pathDesc":"Find by id",
    "rpcType":"http",
    "host":"172.18.23.94",
    "port":8188,
    "ruleName":"/http/order/findById",
    "enabled":true,
    "registerMetaData":false
}

soul-admin会根据请求body里面的contextPath生成selector,根据ruleName生成rule。如果不存在同名的rule,则插入一条rule。如果不存在同名的selector,则插入一条selector;如果存在同名的selector,则更新upStream。

大致的过程捋清楚了之后,来看下SpringMvcClientBeanPostProcessor的一些细节。

SpringMvcClientBeanPostProcessor的构造函数里面初始化了一个只有一个线程的线程池,如下所示。后面是使用该线程池来将发送http请求将API注册到网关管理后台去的。

executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

这符合阿里巴巴Java开发手册里面关于线程相关的规范,如”线程资源必须通过线程池提供,不允许在应用中自行显示创建线程“、”线程池通过ThreadPoolExecutor创建“

soul网关-5-普通springboot项目接入soul网关的原理_第3张图片


上文说到,如果想要soul代理你的整个项目的话,需要在你的springboot项目的application.yml里面,把soul.http.full设置为true。如果soul.http.full是true的话,在SpringMvcClientBeanPostProcessorpostProcessAfterInitialization里面会直接返回bean,并不会利用反射获取注解。

那么如果soul.http.full是true,是怎么代理你的整个项目的呢?

我们可以在soul-client/soul-client-http/soul-client-springmvc模块里面,找到与SpringMvcClientBeanPostProcessor.java处在同一位置的ContextRegisterListener.java,它实现了ApplicationListener,监听ContextRefreshedEvent事件

当ioc容器加载处理完相应的bean之后,spring ioc容器会有一个发布事件的动作,我们可以监听ContextRefreshedEvent事件,在里面去做一些自己想做的事。

soul网关-5-普通springboot项目接入soul网关的原理_第4张图片

因为系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)这种情况下,就会造成onApplicationEvent方法被执行两次。

所以soul网关在ContextRegisterListener.java里面定义了一个AtomicBoolean类型的标志位,用来判断是否已经注册过。

本文只是对普通的springboot/springmvc项目如何接入soul网关做了一点学习,后续会对其他类型的用户如dubbo、spring cloud如何接入soul网关进行学习。

你可能感兴趣的:(Java,网关)