soul源码分析(3)SpringCloud服务如何接入soul以及SpringCloud插件分析

16 篇文章 0 订阅
11 篇文章 0 订阅

说明

本文将包括如下内容:

  • 如何将SpringCloud服务接入soul
  • soul的SpringCloud插件源码分析

本文将以2021.1.25的soul源码进行分析。

1. 如何将SpringCloud服务接入soul

1.1 配置soul网关、soul-admin

  • (1)启动soul-admin后台,操作步骤可以参考本系列第一篇文章阅读源码准备与soul基础

  • (2)在soul-admin后台将SpringCloud插件打开

    • 路径:System Manage -> Plugin -> 打开SpringCloud即可
  • (3)修改网关程序soul-bootstrap,引入SpringCloud相关依赖、添加注册中心配置。注意需要根据注册中心是哪一种、相应配置与依赖需要对应。比如下面主要以eureka为例。更多信息请参考官网:https://dromara.org/zh-cn/docs/soul/user-springcloud.html

    • (3).1引入依赖如下

       <!--soul SpringCloud plugin start-->
        <dependency>
             <groupId>org.dromara</groupId>
             <artifactId>soul-spring-boot-starter-plugin-springcloud</artifactId>
              <version>${last.version}</version>
        </dependency>
      
        <dependency>
             <groupId>org.dromara</groupId>
             <artifactId>soul-spring-boot-starter-plugin-httpclient</artifactId>
             <version>${last.version}</version>
         </dependency>
         <!--soul SpringCloud plugin end-->
         <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-commons</artifactId>
              <version>2.2.0.RELEASE</version>
         </dependency>
         <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
              <version>2.2.0.RELEASE</version>
         </dependency>
      
    • 此处以eureka作为配置中心,还需要添加以下依赖:

      <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
             <version>2.2.0.RELEASE</version>
        </dependency>
      
    • (3).2配置文件添加eureka配置:

       eureka:
           client:
             serviceUrl:
               defaultZone: http://localhost:8761/eureka/ # 你的eureka地址
           instance:
             prefer-ip-address: true # 注册到eureka是IP形式
      
    • (3).3 重启soul-bootstrap

1.2 SpringCloud服务接入soul

下面仍然以soul官方提供的例子soul-examples-springcloud来演示操作步骤。

  • (1)在SpringCloud应用中添加如下依赖:

     <dependency>
          <groupId>org.dromara</groupId>
          <artifactId>soul-spring-boot-starter-client-springcloud</artifactId>
          <version>${last.version}</version>
     </dependency>
    
  • (2)修改SpringCloud的配置:

    soul:
      springcloud:
        admin-url: http://localhost:9095
        context-path: /springcloud
        full: true
    # adminUrl: 为你启动的soul-admin 项目的ip + 端口,注意要加http://
    # contextPath: 为你的这个项目在soul网关的路由前缀,比如/order ,/product 等等,网关会根据你的这个前缀来进行路由.
    # full: 设置true 代表代理你的整个服务,false表示代理你其中某几个controller
    
  • (3)在SpringCloud项目中Controller接口中添加注解,具体请参考示例:https://dromara.org/zh-cn/docs/soul/user-springcloud.html

  • (4)启动SpringCloud项目,启动日志中有如下内容,则说明接口已注入到soul网关中。

2021-01-23 11:50:44.917  INFO 14008 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : springCloud client register success: {"appName":"springCloud-test","context":"/springcloud","path":"/springcloud/order/save","pathDesc":"","rpcType":"springCloud","ruleName":"/springcloud/order/save","enabled":true} 
2021-01-23 11:50:44.928  INFO 14008 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : springCloud client register success: {"appName":"springCloud-test","context":"/springcloud","path":"/springcloud/order/findById","pathDesc":"","rpcType":"springCloud","ruleName":"/springcloud/order/findById","enabled":true} 
2021-01-23 11:50:44.937  INFO 14008 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : springCloud client register success: {"appName":"springCloud-test","context":"/springcloud","path":"/springcloud/order/path/**/name","pathDesc":"","rpcType":"springCloud","ruleName":"/springcloud/order/path/**/name","enabled":true} 
2021-01-23 11:50:44.946  INFO 14008 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : springCloud client register success: {"appName":"springCloud-test","context":"/springcloud","path":"/springcloud/order/path/**","pathDesc":"","rpcType":"springCloud","ruleName":"/springcloud/order/path/**","enabled":true} 
2021-01-23 11:50:44.955  INFO 14008 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : springCloud client register success: {"appName":"springCloud-test","context":"/springcloud","path":"/springcloud/test/**","pathDesc":"","rpcType":"springCloud","ruleName":"/springcloud/test/**","enabled":true} 

此时soul-admin中可以看到SpringCloud插件中已注册的路由信息:

在这里插入图片描述

1.3 测试

soul-examples-springcloud为例,使用curl命令直接访问SpringCloud后端服务:

$ curl -s http://localhost:8884/order/findById?id=2
{"id":"2","name":"hello world spring cloud findById"}

访问网关:

$ curl -s http://localhost:9195/springcloud/order/findById?id=2
{"id":"2","name":"hello world spring cloud findById"}

当然此处也可以使用wrk或是ab等工具做下压测,看通过网关访问与直接访问相比,有多少性能损耗。此处暂时略过。

2. SpringCloud插件源码分析

2.1 Spring Cloud服务怎样注册到soul网关上?

还是从日志开始,SpringCloud服务启动时有这条日志client.common.utils.RegisterUtils : springCloud client register success,那么据此我们可以找到RegisterUtils这个类的doRegister,然后需要看到底是哪些地方调用了这个方法。不过这个工具方法调用的地方比较多,我们可以换一种思路,从soul源码着手。

对于SpringCloud,我们去soul源码中看对应客户端soul-spring-boot-starter-client-springcloud,可以看到只有一个类SoulSpringCloudClientConfiguration:

@Configuration
public class SoulSpringCloudClientConfiguration {
    
    @Bean
    public SpringCloudClientBeanPostProcessor springCloudClientBeanPostProcessor(final SoulSpringCloudConfig soulSpringCloudConfig, final Environment env) {
        return new SpringCloudClientBeanPostProcessor(soulSpringCloudConfig, env);
    }

    @Bean
    public ContextRegisterListener contextRegisterListener(final SoulSpringCloudConfig soulSpringCloudConfig, final Environment env) {
        return new ContextRegisterListener(soulSpringCloudConfig, env);
    }
    
    @Bean
    @ConfigurationProperties(prefix = "soul.springcloud")
    public SoulSpringCloudConfig soulSpringCloudConfig() {
        return new SoulSpringCloudConfig();
    }
}

SoulSpringCloudConfig与配置相关、可以利用这个bean拿到配置文件中springcloud.admin-url, springcloud.context-pathspringcloud.full这3个属性值,暂且不管。

soulSpringCloud客户端主要逻辑就应该在SpringCloudClientBeanPostProcessorContextRegisterListener

先看下SpringCloudClientBeanPostProcessor,明显是一个Spring后置处理器,主要逻辑其实就是筛选使用SoulSpringCloudClient标注的类(controller)、或是方法,将其对应的接口说明(URL、参数类型、参数名等)汇总到一起、序列化成JSON格式,然后发送给soul-admin

@Slf4j
public class SpringCloudClientBeanPostProcessor implements BeanPostProcessor {
	......

    @Override
    public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
        if (config.isFull()) {
            //全量代理,则不用走下面逻辑,直接依靠ContextRegisterListener中的监听逻辑处理即可
            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) {
            String prePath = "";
            SoulSpringCloudClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringCloudClient.class);
            if (Objects.nonNull(clazzAnnotation)) {
                if (clazzAnnotation.path().indexOf("*") > 1) {
                //如果在Controller上声明了正则、匹配所有,则此处将匹配所有这条规则同步给soul-admin后直接返回即可
                    String finalPrePath = prePath;
                    executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
                            RpcTypeEnum.SPRING_CLOUD));
                    return bean;
                }
                prePath = clazzAnnotation.path();
            }
            final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
            for (Method method : methods) {
                //挨个遍历方法,标注了SoulSpringCloudClient的才同步给soul-admin
                SoulSpringCloudClient soulSpringCloudClient = AnnotationUtils.findAnnotation(method, SoulSpringCloudClient.class);
                if (Objects.nonNull(soulSpringCloudClient)) {
                    String finalPrePath = prePath;
                    executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringCloudClient, finalPrePath), url,
                            RpcTypeEnum.SPRING_CLOUD));
                }
            }
        }
        return bean;
    }

    private String buildJsonParams(final SoulSpringCloudClient soulSpringCloudClient, final String prePath) {
		//拼接参数,序列化成json字符串
        ......
        return OkHttpTools.getInstance().getGson().toJson(registerDTO);
    }
}

ContextRegisterListener内容则更为简单,利用事件监听机制,Spring容器更新时扫描一次,只有当配置了所有接口都交给soul代理时,才会执行一次,将"匹配所有URL"的规则注册到soul-admin上。

public class ContextRegisterListener implements ApplicationListener<ContextRefreshedEvent> {

    private final AtomicBoolean registered = new AtomicBoolean(false);

    private final String url;

    private final SoulSpringCloudConfig config;

    private final Environment env;

    /**
     * Instantiates a new Context register listener.
     *
     * @param config the soul spring cloud config
     * @param env    the env
     */
    public ContextRegisterListener(final SoulSpringCloudConfig config, final Environment env) {
        ValidateUtils.validate(config, env);
        this.config = config;
        this.env = env;
        this.url = config.getAdminUrl() + "/soul-client/springcloud-register";
    }

    @Override
    public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
        if (!registered.compareAndSet(false, true)) {
            return;
        }
        if (config.isFull()) {
            //配置属性soul.springcloud.full=true,则将会把所有接口都注册到soul-admin上
            RegisterUtils.doRegister(buildJsonParams(), url, RpcTypeEnum.SPRING_CLOUD);
        }
    }

    private String buildJsonParams() {
        String contextPath = config.getContextPath();
        String appName = env.getProperty("spring.application.name");
        //指定所有URL都匹配
        String path = contextPath + "/**";
        //拼接参数
		......
        return OkHttpTools.getInstance().getGson().toJson(registerDTO);
    }
}

所以SpringCloud客户端注册到soul-admin主要就依赖的是SpringCloudClientBeanPostProcessorContextRegisterListener这两个类。

2.2 soul网关中Spring Cloud插件如何代理用户请求?

根据之前的分析soul源码分析总结篇之插件化设计soul服务端如何处理SpringCloud只需要关注于SpringCloudPlugin中的doExecute方法:

public class SpringCloudPlugin extends AbstractSoulPlugin {
	......

    @Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        if (Objects.isNull(rule)) {
            //没有配置规则,直接返回即可
            return Mono.empty();
        }
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        final SpringCloudRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), SpringCloudRuleHandle.class);
        final SpringCloudSelectorHandle selectorHandle = GsonUtils.getInstance().fromJson(selector.getHandle(), SpringCloudSelectorHandle.class);
        if (StringUtils.isBlank(selectorHandle.getServiceId()) || StringUtils.isBlank(ruleHandle.getPath())) {
            //找不到规则,报错
            Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_CONFIG_SPRINGCLOUD_SERVICEID.getCode(), SoulResultEnum.CANNOT_CONFIG_SPRINGCLOUD_SERVICEID.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
		//负载均衡
        final ServiceInstance serviceInstance = loadBalancer.choose(selectorHandle.getServiceId());
        if (Objects.isNull(serviceInstance)) {
            //根据servierId找不到后端服务,报错
            Object error = SoulResultWrap.error(SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getCode(), SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        //拼接参数
        final URI uri = loadBalancer.reconstructURI(serviceInstance, URI.create(soulContext.getRealUrl()));

        String realURL = buildRealURL(uri.toASCIIString(), soulContext.getHttpMethod(), exchange.getRequest().getURI().getQuery());

        exchange.getAttributes().put(Constants.HTTP_URL, realURL);
        //set time out.
        exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
        //责任链,继续执行后续逻辑,具体请求交给后续处理
        return chain.execute(exchange);
    }
    ......
}

总结

  • 如何将SpringCloud接入soul
  • soul客户端、服务端是如何处理SpringCloud请求的
  • soul-admin同步数据给soul-bootstrap将在后续单独分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值