Soul网关中的@SoulSpringMvcClient注解

本篇文章分析的是@SoulSpringMvcClient注解,它的作用是:用于标记SpringMvc服务中的接口,被标记的接口在系统启动的是时候,将当前接口注册到soul-admin后台中。使用方式如下:


@RestController
@RequestMapping("/order")
@SoulSpringMvcClient(path = "/order")
public class OrderController {
	//省略了其他代码

    @GetMapping("/findById")
    @SoulSpringMvcClient(path = "/findById", desc = "Find by id")
    public OrderDTO findById(@RequestParam("id") final String id) {
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setId(id);
        orderDTO.setName("hello world findById");
        return orderDTO;
    }
}

它的定义如下:

  • 注解可以使用在类上,也可以使用在方法上;
  • path:表示接口的路径;
  • ruleName:表示规则名称;
  • desc:接口描述信息;
  • rpcType:传输类型,默认是http,在soul网关中,还有SpringCloudDubboSofa等类型;
  • enabled:是否开启(是否被网关代理),默认是true:
  • registerMetaData:是否注册元数据信息,默认是false
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface SoulSpringMvcClient {
    
    /**
     * Path string.
     *
     * @return the string
     */
    String path();
    
    /**
     * Rule name string.
     *
     * @return the string
     */
    String ruleName() default "";
    
    /**
     * Desc string.
     *
     * @return String string
     */
    String desc() default "";
    
    /**
     * Rpc type string.
     *
     * @return the string
     */
    String rpcType() default "http";
    
    /**
     * Enabled boolean.
     *
     * @return the boolean
     */
    boolean enabled() default true;
    
    /**
     * Register meta data boolean.
     *
     * @return the boolean
     */
    boolean registerMetaData() default false;
}

注册过程在启动的时候完成,以soul官网中的soul-examples-http为例,它的案例演示请参考前面的文章。在配置文件中配置soul-admin后台信息:

soul:
  http:
    adminUrl: http://localhost:9095 #soul-admin后台地址
    port: 8188 # soul-admin后台端口
    contextPath: /http # 当前服务的上下文路径
    appName: http # 当前服务的名称
    full: false  # 是否全部被代理如果为true那么这个服务的所有接口都会被代理就不用加注解了soul-admin端也就不需要规则只需要一个选择器

业务系统启动时,会通过start加载配置文件,SpringMvcClientBeanPostProcessor这个后置处理器也会被加载。

@SoulSpringMvcClient注解的处理就在这个后置处理器中:

  • 配置如果是full=true,就返回,表示处理所有;
  • 获取当前BeanControllerRestControllerRequestMapping,注解,判断是否包含"*"。如果是,就代理所有方法,注册当前Bean
  • 获取所有方法,注册每个含有@SoulSpringMvcClient注解的方法。

public class SpringMvcClientBeanPostProcessor implements BeanPostProcessor {

	//省略了其他代码

    @Override
    public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
        //配置是否为true
        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) {
            SoulSpringMvcClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
            String prePath = "";
            if (Objects.nonNull(clazzAnnotation)) {
                if (clazzAnnotation.path().indexOf("*") > 1) {
                    String finalPrePath = prePath;
                    //注册
                    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;
    }

    //省略了其他代码
}

注册的逻辑就是发请求信息到soul-admin中,这个交给了线程池。什么时候发起请求,是线程池在调度处理,发请求的方式是通过OkHttp发起post请求。

//org.dromara.soul.client.common.utils.RegisterUtils#doRegister    
public static void doRegister(final String json, final String url, final RpcTypeEnum rpcTypeEnum) {
        try {
            //通过OkHttp发起post请求
            String result = OkHttpTools.getInstance().post(url, json);
            if (AdminConstants.SUCCESS.equals(result)) {
                log.info("{} client register success: {} ", rpcTypeEnum.getName(), json);
            } else {
                log.error("{} client register error: {} ", rpcTypeEnum.getName(), json);
            }
        } catch (IOException e) {
            log.error("cannot register soul admin param, url: {}, request body: {}", url, json, e);
        }
    }

soul-admin端有个接口来接受当前的注解配置信息,这里面的逻辑就是创建或者更新选择器或插件信息。

@RestController
@RequestMapping("/soul-client")
public class SoulClientController {

    //省略了其他代码

    /**
     * Register spring mvc string.
     *
     * @param springMvcRegisterDTO the spring mvc register dto
     * @return the string
     */
    @PostMapping("/springmvc-register")
    public String registerSpringMvc(@RequestBody final SpringMvcRegisterDTO springMvcRegisterDTO) {
        return soulClientRegisterService.registerSpringMvc(springMvcRegisterDTO);
    }
}

通过上面的分析,就明白了@SoulSpringMvcClient注解的执行过程及原理。