【Soul源码阅读】3.HTTP 用户接入 Soul 流程解析

昨天只是极简入门,关于网关是怎么感知到我们的应用的,相信小伙伴们一定有疑问,今天先来看下 HTTP 用户如何接入 Soul,以及接入的流程是怎样的。

这是官网对于 HTTP 用户的文档,https://dromara.org/zh-cn/docs/soul/user-http.html

官网文档明确指出,Soul 网关使用 divide 插件来处理http请求,我们在 soul-admin 页面中看到,第一个就是 divide 插件,而且是默认开启的。

Http请求接入网关,分2种情况,1种是 springMvc 体系用户,1种是非 springMvc 体系。

对于非 springMvc 体系接入,是通过 soul-admin 的页面操作来完成的,这个流程我们后边再分析。

对于 springMvc 体系用户,通过注解和配置,就自动同步到 divide 插件下的选择器规则列表,我们今天就来一探究竟。

1.配置文件

无论是 Spring Boot 用户,还是 Spring MVC 用户,都需要一段配置(以 Spring Boot 为例)

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

回到我们的测试项目 soul-examples-http 中,查看其配置文件 application.yml

soul:
  http:
    adminUrl: http://localhost:9095
    port: 8188
    contextPath: /http
    appName: http
    full: false

在 http 层级下任一属性上,Ctrl+B,自动跳转到 Bean 类 SoulSpringMvcConfig。

@Data
public class SoulSpringMvcConfig {

    private String adminUrl;

    private String contextPath;

    private String appName;

    /**
     * Set true means providing proxy for your entire service, or only a few controller.
     */
    private boolean full;

    private String host;

    private Integer port;
}

不得不佩服 IDEA 的开发团队,好多功能简直太强大太方便了,在 SoulSpringMvcConfig 类左边,直接导航到 Spring Bean 的定义。

直接就跳转到这里了,SoulSpringMvcClientConfiguration 这个类,上代码

/**
 * Soul http config soul http config.
 *
 * @return the soul http config
 */
@Bean
@ConfigurationProperties(prefix = "soul.http")
public SoulSpringMvcConfig soulHttpConfig() {
	return new SoulSpringMvcConfig();
}

看下这个类的坐标,又看到了 spring.factories 和 spring.providers,是不是想起了什么?对的,聪明的你一定想到了,那就是 Spring Boot Starter。

# spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration

# spring.provides
provides: soul-spring-boot-starter-client-springmvc

关于 Spring Boot Starter 这里就不多介绍了,感兴趣的小伙伴,可以自行学习。

2.注解 @SoulSpringMvcClient

可以看到 SoulSpringMvcClient 注解定义,既可以加在类上,也可以加在方法上。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SoulSpringMvcClient{
    
    String path();

    String ruleName() default "";
    
    String desc() default "";
    
    String rpcType() default "http";
    
    boolean enabled() default true;
    
    boolean registerMetaData() default false;

}

查找该注解在哪里引用了,除了测试用例及 import 导包,可以看到有2个地方引用了,点进去一探究竟。

首先看下这个类的定义,实现了 BeanPostProcessor 接口,重写了 postProcessAfterInitialization 方法。

public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor

再次看到这熟悉的图标 ,看看这个类在哪里注入到 Spring IoC 容器里的。再次跳转到了这个类 SoulSpringMvcClientConfiguration,跟上面 SoulSpringMvcConfig 类的注入都是这个类,并且把 SoulSpringMvcConfig 作为构造参数传进去了。

@Configuration
public class SoulSpringMvcClientConfiguration {
    
    /**
     * Spring http client bean post processor spring http client bean post processor.
     *
     * @param soulSpringMvcConfig the soul http config
     * @return the spring http client bean post processor
     */
    @Bean
    public SpringMvcClientBeanPostProcessor springHttpClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
        return new SpringMvcClientBeanPostProcessor(soulSpringMvcConfig);
    }

...

}

好吧,到这里,我们再回到 SpringMvcClientBeanPostProcessor,看看里面具体的逻辑是怎样的。

先来看下类属性和构造方法。

private final ThreadPoolExecutor executorService;

private final String url;

private final SoulSpringMvcConfig soulSpringMvcConfig;

/**
 * Instantiates a new Soul client bean post processor.
 *
 * @param soulSpringMvcConfig the soul spring mvc config
 */
public SpringMvcClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
	ValidateUtils.validate(soulSpringMvcConfig);
	this.soulSpringMvcConfig = soulSpringMvcConfig;
	url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
	executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}

构造方法第1行,对 soulSpringMvcConfig 做了个参数校验,保证 contextPath、adminUrl、port 参数都有值,否则报错。

/**
 * validate SoulSpringMvcConfig.
 *
 * @param soulSpringMvcConfig the soulSpringMvcConfig
 * @throws RuntimeException the RuntimeException
 */
public static void validate(final SoulSpringMvcConfig soulSpringMvcConfig) {
	String contextPath = soulSpringMvcConfig.getContextPath();
	String adminUrl = soulSpringMvcConfig.getAdminUrl();
	Integer port = soulSpringMvcConfig.getPort();
	if (StringUtils.isNotBlank(contextPath) && StringUtils.isNotBlank(adminUrl) && port != null) {
		return;
	}
	String errorMsg = "spring mvc param must config contextPath, adminUrl and port";
	log.error(errorMsg);
	throw new RuntimeException(errorMsg);
}

构造方法把前面在配置文件中配置的属性传了进来,第3行,把 adminUrl 拼接成了 URL,可以预见,这个就是要调用 soul-admin 接口,实现自动注册的关键,我们拭目以待。第4行,启动了一个固定线程为1的线程池。

好了,终于来到了最核心的逻辑(postProcessAfterInitialization 方法),就是如何把前面这些内容整合利用起来,上代码。

  1. @Override
  2. public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
  3.     if (soulSpringMvcConfig.isFull()) {
  4.         return bean;
  5.     }
  6.     Controller controller = AnnotationUtils.findAnnotation(bean.getClass(), Controller.class);
  7.     RestController restController = AnnotationUtils.findAnnotation(bean.getClass(), RestController.class);
  8.     RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
  9.     if (controller != null || restController != null || requestMapping != null) {
  10.         SoulSpringMvcClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
  11.         String prePath = "";
  12.         if (Objects.nonNull(clazzAnnotation)) {
  13.             if (clazzAnnotation.path().indexOf("*") > 1) {
  14.                 String finalPrePath = prePath;
  15.                 executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
  16.                         RpcTypeEnum.HTTP));
  17.                 return bean;
  18.             }
  19.             prePath = clazzAnnotation.path();
  20.         }
  21.         final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(bean.getClass());
  22.         for (Method method : methods) {
  23.             SoulSpringMvcClient soulSpringMvcClient = AnnotationUtils.findAnnotation(method, SoulSpringMvcClient.class);
  24.             if (Objects.nonNull(soulSpringMvcClient)) {
  25.                 String finalPrePath = prePath;
  26.                 executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(soulSpringMvcClient, finalPrePath), url,
  27.                         RpcTypeEnum.HTTP));
  28.             }
  29.         }
  30.     }
  31.     return bean;
  32. }

第3行,soulSpringMvcConfig.isFull(),我的理解是,如果配置成 true,整个系统都被网关代理,就无需每个Controller 上都加注解了。(不知道对不对,后续遇到为 true 的情况再补充,存疑!)

6~9行,这里只处理 SpringMVC Controller。

10~20行,处理在类上加注解的情况。而22~29行,处理在方法上加注解的情况。

处理逻辑类似,都是根据注解拿到注解具体内容,将注解的内容加上配置文件内容封装,通过 RegisterUtils 这个工具类的 doRegister 方法,调用 soul-admin 方法,将需要网关拦截的接口信息发送过去并持久化。

3.总结

好的,到这里,我们就把 SpringMVC 接入 Soul 网关的流程打通了。

回顾一下,用户通过 配置文件 + 注解,将需要接入 Soul 网关的接口信息呈现出来,网关使用 Spring Boot Starter,将信息封装,通过调用 soul-admin 接口的方式注册。

今天就先到这里吧,明天见。

猜你喜欢

转载自blog.csdn.net/hellboy0621/article/details/112688012
今日推荐