如果有多个产品线售卖API,那么他们都会有一些共性的需求,例如签名认证、限速等,为了避免每个产品线重复开发,那么可以实现一个网关系统来统一提供这些功能。
那么大致的流程是这样的:
前几篇笔记里面,学习了soul网关divide插件如何进行请求代理和数据同步机制之websocket,对上述步骤里面的2和4有了大概的了解。
在今天的源码学习之前,可以设想一下,假如让我来为各产品线提供一个注册API到网关管理后台的方案的话,可能是这样的:
这个方案的缺点是,有一定的学习成本,提供的接口放在一个文档里面,各个产品线的RD需要自行阅读和学习;接入时间长,从开发到调试到正式接入,至少要两天的时间;对产品线的业务或者业务后台有比较大的侵入性,总要在某个地方有一些代码是将API注册到网关管理后台的。总体来说就是不够无痛。
本文就来学习一下soul网关项目是如何简便地让产品线将要售卖的API注册到网关管理后台的。
首先看一下官方文档里面关于项目接入的介绍,就先看普通的springboot项目如何接入soul网关,很方便,只需要几个步骤就可以,分别是:
soul-spring-boot-starter-client-springmvc
依赖
org.dromara
soul-spring-boot-starter-client-springmvc
${last.version}
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
注解
@SoulSpringMvcClient
注解@SoulSpringMvcClient
注解启动你的springboot项目之后,你的接口就接入到了网关
register success
字样的日志整个过程感觉非常顺畅,从官方文档里面给出的信息可以知道,接入方式这么简便,最主要的是使用了@SoulSpringMvcClient
这个soul自定义的注解。
那么就可以猜测大致的接入过程是这样的:
soul-spring-boot-starter-client-springmvc
依赖。你的项目启动后,这个依赖会扫描你项目中有@SoulSpringMvcClient
注解的controller以及接口@SoulSpringMvcClient
注解的时候给这个注解传递的path值,可以知道你要注册到网关的接口
@SoulSpringMvcClient(path = "/hi")
,那么其实是把/hi
注册到了网关/myProject
,你要注册的接口是/hello
,网关的对外域名是xxxgateway.com
。那么外部用户就通过请求http://xxxgateway.com/myProject/hello
来访问你的/hello接口。我们以@SoulSpringMvcClient
为切入点来具体看一下soul网关的源码,看一下与我们猜测的是否符合,学习一下里面的细节。
看一下@SoulSpringMvcClient
的定义,里面有path、ruleName等属性,是一个普通的注解
一般都是通过反射拿到的注解,全局搜索一下,看下哪里通过反射拿到了@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
接口里有两个方法,分别是postProcessBeforeInitialization
和postProcessAfterInitialization
,分别在 bean初始化方法调用前被调用,和 在bean初始化法调用后被调用
看下SpringMvcClientBeanPostProcessor
对于上述两个方法的实现。发现它Override了postProcessAfterInitialization
方法。里面的逻辑大致就是像我们上面猜测的那样:通过反射获取声明了@SoulSpringMvcClient
注解的类和方法,将他们的信息注册到网关管理后台去,网关管理后台用来接收API注册的url是http://localhost:9095/soul-client/springmvc-register
。
这里有几个注意事项:
SpringMvcClientBeanPostProcessor
的postProcessAfterInitialization
里面,会对每个bean都通过反射获取并判断它是否有@Controller
、@RestController
、@RequestMapping
注解。
@SoulSpringMvcClient
注解是代表注册整个controller里面的所有接口的,例如HttpTestController
上面的注解是@SoulSpringMvcClient(path = "/test/**")
,它有n个接口,它只会在soul-admin管理后台生成一条相应的rule,并不会生成n条rulefinal Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
@SoulSpringMvcClient
注解@SoulSpringMvcClient
注解,那么该方法会被注册到网关管理后台@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代理你的整个项目的话,需要在你的springboot项目的application.yml里面,把soul.http.full设置为true。如果soul.http.full是true的话,在SpringMvcClientBeanPostProcessor
的postProcessAfterInitialization
里面会直接返回bean,并不会利用反射获取注解。
那么如果soul.http.full是true,是怎么代理你的整个项目的呢?
我们可以在soul-client/soul-client-http/soul-client-springmvc
模块里面,找到与SpringMvcClientBeanPostProcessor.java
处在同一位置的ContextRegisterListener.java
,它实现了ApplicationListener
,监听ContextRefreshedEvent
事件
当ioc容器加载处理完相应的bean之后,spring ioc容器会有一个发布事件的动作,我们可以监听ContextRefreshedEvent
事件,在里面去做一些自己想做的事。
因为系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)这种情况下,就会造成onApplicationEvent方法被执行两次。
所以soul网关在ContextRegisterListener.java
里面定义了一个AtomicBoolean
类型的标志位,用来判断是否已经注册过。
本文只是对普通的springboot/springmvc项目如何接入soul网关做了一点学习,后续会对其他类型的用户如dubbo、spring cloud如何接入soul网关进行学习。