Soul源码阅读00-springMvc体系下的接口注册

·  阅读 211

1 Soul概览

什么是Soul?

Soul 是基于 WebFlux 实现的响应式的 API 网关,具有异步、高性能、跨语言等特点(JAVA语言编写)。

Soul有哪些支持?

由于Soul网关的接入是通过Http请求接入,所以Soul天然支持多语言(和Nacos一样),不仅如此soul还支持Dubbo的泛化调用,SpringCloud,SpringBoot,SpringMvc等。

Soul有些特点?

  • 基于Netty编写(NIO响应更快)
  • 良好的WebUI支持(很棒的)
  • 丰富的插件,包括但不限于鉴权、限流、熔断、防火墙、负载均衡等。
  • 配置的完全的动态更新(通过WebUI界面修改立马生效,无需重启)。
  • 等等.........

Soul的相关文档

官方文档写了非常详尽的教程所以此处不再进行相关部署讲解,参考链接如下

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-adminsoul的控制台
soul-bootstrapsoul的网关服务
soul-clientsoul对客户端支持的相关代码
soul-commonsoul的一些公用类?
soul-dlist暂时不知道啥东西
soul-examplessoul提供的各种客户端接入的示例
soul-metricssoul的指标?疑似soul的监控相关模块?
soul-pluginsoul的插件
soul-register-centersoul的注册中心?代码还是基本代码 疑似同步其他注册中心数据用的
soul-spisoul提供的spi扩展的支持?
soul-spring-boot-starterspring下各种环境集成的starter的配置文件 核心代码还是在soul-client中
soul-sync-data-centersoul的数据同步中心 这里指的是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时整体的注册。

分类:
后端
标签: