1 Soul概览
什么是Soul?
Soul 是基于 WebFlux 实现的响应式的 API 网关,具有异步、高性能、跨语言等特点(JAVA语言编写)。
Soul有哪些支持?
由于Soul网关的接入是通过Http请求接入,所以Soul天然支持多语言(和Nacos一样),不仅如此soul还支持Dubbo的泛化调用,SpringCloud,SpringBoot,SpringMvc等。
Soul有些特点?
- 基于Netty编写(NIO响应更快)
- 良好的WebUI支持(很棒的)
- 丰富的插件,包括但不限于鉴权、限流、熔断、防火墙、负载均衡等。
- 配置的完全的动态更新(通过WebUI界面修改立马生效,无需重启)。
- 等等.........
Soul的相关文档
- git地址 github.com/dromara/sou…
- gitte地址 gitee.com/shuaiqiyu/s…
- 官方文档 dromara.org/zh-cn/docs/…
官方文档写了非常详尽的教程所以此处不再进行相关部署讲解,参考链接如下
2 Soul环境编译
1.git clone https://github.com/dromara/soul.git
2.mvn clean package install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true -Dcheckstyle.skip=true
注意:idea2020.3版本由于和lombok的“一些矛盾”导致可能编译失败,若编译失败增加“-Djps.track.ap.dependencies=false”重新编译
3 Soul 工程概览
| 工程名 | 工程作用(猜测) |
|---|---|
| soul-admin | soul的控制台 |
| soul-bootstrap | soul的网关服务 |
| soul-client | soul对客户端支持的相关代码 |
| soul-common | soul的一些公用类? |
| soul-dlist | 暂时不知道啥东西 |
| soul-examples | soul提供的各种客户端接入的示例 |
| soul-metrics | soul的指标?疑似soul的监控相关模块? |
| soul-plugin | soul的插件 |
| soul-register-center | soul的注册中心?代码还是基本代码 疑似同步其他注册中心数据用的 |
| soul-spi | soul提供的spi扩展的支持? |
| soul-spring-boot-starter | spring下各种环境集成的starter的配置文件 核心代码还是在soul-client中 |
| soul-sync-data-center | soul的数据同步中心 这里指的是soul接入的各种地址通过某些方式同步到JVM内存中 |
4 Soul的简单使用
基于SpringMvc的Http的实现原理
- 相关配置
soul:
http:
adminUrl: http://localhost:9095
port: 8188
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
- 初始化项目先加载了三个bean ContextRegisterListener SpringMvcClientBeanPostProcessor都基于SoulSpringMvcConfig进行了装配
// spring Http 请求bean处理器
@Bean
public SpringMvcClientBeanPostProcessor springHttpClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
return new SpringMvcClientBeanPostProcessor(soulSpringMvcConfig);
}
//上下文注册监听
@Bean
public ContextRegisterListener contextRegisterListener(final SoulSpringMvcConfig soulSpringMvcConfig) {
return new ContextRegisterListener(soulSpringMvcConfig);
}
// 配置
@Bean
@ConfigurationProperties(prefix = "soul.http")
public SoulSpringMvcConfig soulHttpConfig() {
return new SoulSpringMvcConfig();
}
- 查看 SpringMvcClientBeanPostProcessor类的,发现其实现了BeanPostProcessor接口的postProcessBeforeInitialization方法,(这里需要注意springmvc-client是基于springBoot2.2.2编译,即使用Java8写的 所以BeanPostProcessor得接口方法都是default,若低版本引用的化由于没有实现before的方法会出现抽象方法错误问题,需自己进行二次编译)。
- 查看 SpringMvcClientBeanPostProcessor类的postProcessBeforeInitialization方法,看到了非常熟悉的操作,经常封装的同学应该经常用,扫包获取注解。
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
//需要全局代理 直接跳出 交给ContextRegisterListener 实现了
if (soulSpringMvcConfig.isFull()) {
return bean;
}
Controller controller = AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
RestController restController = AnnotationUtils.findAnnotation(bean.getClass(), RestController.class);
RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
if (controller != null || restController != null || requestMapping != null) {
//满足MVC 的情况后 查找SoulSpringMvcClient注解
SoulSpringMvcClient注解 clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
String prePath = "";
//判断是否需要注册到soul
if (Objects.nonNull(clazzAnnotation)) {
//判断是否需要全局代理这个类下的所有接口
if (clazzAnnotation.path().indexOf("*") > 1) {
String finalPrePath = prePath;
// 单例线程池注册 确保了不会影响服务启动 新技能Get(之间没想到过)
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
RpcTypeEnum.HTTP));
return bean;
}
prePath = clazzAnnotation.path();
}
//不需要全局代理的化拿到所有的接口进行循环判断+注册
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
for (Method method : methods) {
SoulSpringMvcClient soulSpringMvcClient = AnnotationUtils.findAnnotation(method, SoulSpringMvcClient.class);
if (Objects.nonNull(soulSpringMvcClient)) {
String finalPrePath = prePath;
executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringMvcClient, finalPrePath), url,
RpcTypeEnum.HTTP));
}
}
}
return bean;
}
- ContextRegisterListener类 实现了ApplicationListener<ContextRefreshedEvent 1>,在这里实现了onApplicationEvent<ContextRefreshedEvent 1>,实现了整理类加载完成后的操作,此时这里的soulSpringMvcConfig.isFull() 就和前面那个首尾呼应,将整个系统的以/xx/**的方式注册到了soul中,个人理解 配置full:true时不应该是有没有注解修饰的方法都应该发现并注册上去吗?先mark,后期看到请求转发再看具体处理逻辑。
private final AtomicBoolean registered = new AtomicBoolean(false);
/***
省略代码
***/
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
//这里的CAS替换操作不太理解 先mark
//补充 20201115:这的cas操作是会了反制多次刷新 导致重复注册 所有有个cas操作代表只操作一次
if (!registered.compareAndSet(false, true)) {
return;
}
//整个系统的以/xx/**的方式注册上去
if (soulSpringMvcConfig.isFull()) {
RegisterUtils.doRegister(buildJsonParams(), url, RpcTypeEnum.HTTP);
}
}
- 注册到DTO是这样的
public class SpringMvcRegisterDTO {
//应用名称
private String appName;
//前置路径
private String context;
//soul转发的路径=context+/自己的接口地址
private String path;
//地址详情
private String pathDesc;
//类型 SpringMvc下都是 RpcTypeEnum.HTTP
private String rpcType;
//ip
private String host;
//端口
private Integer port;
//规则? 感觉像是路由的规则 后期看到请求转发那再说
private String ruleName;
//是否启用
private boolean enabled;
//是否注册元数据
private boolean registerMetaData;
}
总结:通过整体的预览,已经明确了在SpringMvc体系下,接口是如何注册到Soul的,首先在项目加载创建了三个Spring的Bean,ContextRegisterListener SpringMvcClientBeanPostProcessor,SoulSpringMvcConfig,其中ContextRegisterListener SpringMvcClientBeanPostProcessor都基于SoulSpringMvcConfig配置类进行了二次装配,再SpringMvcClientBeanPostProcessor通过实现BeanPostProcessor的after方法的操作,实现了对类加载完成后的扫包操作(严格意义不叫扫包),完成了单个接口的注册,而ContextRegisterListener实现了ApplicationListener<ContextRefreshedEvent 1>实现了对full为true时整体的注册。