Apache Shenyu服务注册

329 阅读6分钟

项目介绍

Apache ShenYu 是一个 高性能,多协议,易扩展,响应式的API网关

兼容各种主流框架体系,支持热插拔,用户可以定制化开发,满足用户各种场景的现状和未来需求,经历过大规模场景的锤炼

ShenYu 官网

Github 地址

服务注册

未命名文件

声明注册接口

这里还是以shenyu-examples-http为例,查看流程:

在对应shenyu-examples-http中,我们可以看到引入下面依赖:

<dependency>
     <groupId>org.apache.shenyu</groupId>
     <artifactId>shenyu-spring-boot-starter-client-springmvc</artifactId>
     <version>${project.version}</version>
</dependency>

可以看到,在引用时,引用了对应的client包。

接着看对应服务在进行注册时如何进行调用:

@RestController
@RequestMapping("/order")
@ShenyuSpringMvcClient("/order")
@ApiModule(value = "order")
public class OrderController {
​
    /**
     * Save order dto.
     *
     * @param orderDTO the order dto
     * @return the order dto
     */
    @PostMapping("/save")
    @ShenyuSpringMvcClient("/save")
    @ApiDoc(desc = "save")
    public OrderDTO save(@RequestBody final OrderDTO orderDTO) {
        orderDTO.setName("hello world save order");
        return orderDTO;
    }

可以看使用ShenyuSpringMvcClient注解,这个是即是使用对应的客户端。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ShenyuSpringMvcClient {
    
    /**
     * Path string.
     *
     * @return the string
     */
    @AliasFor(attribute = "path")
    String value() default "";
​
    /**
     * Path string.
     *
     * @return the string
     */
    @AliasFor(attribute = "value")
    String path() default "";
​
    /**
     * Rule name string.
     *
     * @return the string
     */
    String ruleName() default "";
    
    /**
     * Desc string.
     *
     * @return String string
     */
    String desc() default "";
​
    /**
     * Enabled boolean.
     *
     * @return the boolean
     */
    boolean enabled() default true;
    
    /**
     * Register meta data boolean.
     *
     * @return the boolean
     */
    boolean registerMetaData() default true;
}

扫描注解信息

先备注一下:很多文章(甚至包包括官网)中都说再扫描注解信息时使用了SpringMvcClientBeanPostProcessor,这个类在#3484中已经被更改,里面的逻辑没有变换。但是很多代码位置进行了改变。

注解扫描通过ShenyuClientRegisterEventPublisher完成,在构造器例化的过程中:

  • 读取属性配置
  • 添加注解,读取path信息
  • 启动注册中心,向shenyu-admin注册
 public SpringMvcClientEventListener(final PropertiesConfig clientConfig,
                                        final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
        super(clientConfig, shenyuClientRegisterRepository);
        //1.读取属性配置
        Properties props = clientConfig.getProps();
        this.isFull = Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL, Boolean.FALSE.toString()));
        this.protocol = props.getProperty(ShenyuClientConstants.PROTOCOL, ShenyuClientConstants.HTTP);
        this.addPrefixed = Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.ADD_PREFIXED,
                Boolean.FALSE.toString()));
        //2,添加注解
        mappingAnnotation.add(ShenyuSpringMvcClient.class);
        mappingAnnotation.add(RequestMapping.class);
    }
​
//下面时super(clientConfig, shenyuClientRegisterRepository);
public AbstractContextRefreshedEventListener(final PropertiesConfig clientConfig,
                                                 final ShenyuClientRegisterRepository shenyuClientRegisterRepository) {
        //1. 读取属性配置,这些字段是所有插件中应该包含的
        Properties props = clientConfig.getProps();
        this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
        this.contextPath = Optional.ofNullable(props.getProperty(ShenyuClientConstants.CONTEXT_PATH)).map(UriUtils::repairData).orElse("");
        if (StringUtils.isBlank(appName) && StringUtils.isBlank(contextPath)) {
            String errorMsg = "client register param must config the appName or contextPath";
            LOG.error(errorMsg);
            throw new ShenyuClientIllegalArgumentException(errorMsg);
        }
        this.ipAndPort = props.getProperty(ShenyuClientConstants.IP_PORT);
        this.host = props.getProperty(ShenyuClientConstants.HOST);
        this.port = props.getProperty(ShenyuClientConstants.PORT);
        //3.启动注册中心
        publisher.start(shenyuClientRegisterRepository);
    }

在此类初始化完成之后,会读取注解信息,构建元数据对象和URI对象,并向shenyu-admin注册。

//对应父类方法
protected void handle(final String beanName, final T bean) {
        //
        Class<?> clazz = getCorrectedClass(bean);
        final A beanShenyuClient = AnnotatedElementUtils.findMergedAnnotation(clazz, getAnnotationType());
        final String superPath = buildApiSuperPath(clazz, beanShenyuClient);
        // Compatible with previous versions
        if (Objects.nonNull(beanShenyuClient) && superPath.contains("*")) {
            handleClass(clazz, bean, beanShenyuClient, superPath);
            return;
        }
        final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
        for (Method method : methods) {
            handleMethod(bean, clazz, beanShenyuClient, method, superPath);
        }
    }

因为所有插件都是实现对应流程,只是具体插件流程不太一样,因此将其中公共部分进行抽象出来,写道对应的父类之中,下面一个一个方法进行查看,一句一句进行分析:

 protected Class<?> getCorrectedClass(final T bean) {
        Class<?> clazz = bean.getClass();
        if (AopUtils.isAopProxy(bean)) {
            clazz = AopUtils.getTargetClass(bean);
        }
        return clazz;
    }

这的函数的作用是将拿到类对应的反射,而不是类本身

这里利用一个TODO:反射的好处是什么,为什么不直接使用对应的对象。

提高代码的复用率,外部调用方便

再看下面:

//这个作用时读取上面反射过来类上的注解,此处是ShenyuClientMvcClient。
final A beanShenyuClient = AnnotatedElementUtils.findMergedAnnotation(clazz, getAnnotationType());
//构建superPath
final String superPath = buildApiSuperPath(clazz, beanShenyuClient);
// Compatible with previous versions
//是否注册整个方法
if (Objects.nonNull(beanShenyuClient) && superPath.contains("*")) {
//构建元数据对象,然后向shenyu-admin注册
handleClass(clazz, bean, beanShenyuClient, superPath);
return;
}

handlerClass中方法如下:

protected void handleClass(final Class<?> clazz,
                           final T bean,
                           @NonNull final A beanShenyuClient,
                           final String superPath) {
  publisher.publishEvent(buildMetaDataDTO(bean, beanShenyuClient, pathJoin(contextPath, superPath), clazz, null));
}

在进行superPath构建的时候 ,上面 使用了buildApiSuperPath方法,这个方法需要 着重查看一下:

这里的话查看对应SpringMvcClient的逻辑:

//先从类上的注解ShenyuSpringMvcClient取path属性,如果没有,就从当前类的RequestMapping注解中取path信息。
@Override
protected String buildApiSuperPath(final Class<?> clazz, @Nullable final ShenyuSpringMvcClient beanShenyuClient) {
    //先从类上的注解shenyuSpringMvcClient取path属性
    if (Objects.nonNull(beanShenyuClient) && StringUtils.isNotBlank(beanShenyuClient.path())) {
        return beanShenyuClient.path();
    }
    //从当前类中的RequestMapping注解中取path信息
    RequestMapping requestMapping = AnnotationUtils.findAnnotation(clazz, RequestMapping.class);
    // Only the first path is supported temporarily
    if (Objects.nonNull(requestMapping) && ArrayUtils.isNotEmpty(requestMapping.path()) && StringUtils.isNotBlank(requestMapping.path()[0])) {
        return requestMapping.path()[0];
    }
    return "";
}

继续看下面的方法:

//读取所有方法
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
//根据 具体类实现 其中具体逻辑
for (Method method : methods) {
    handleMethod(bean, clazz, beanShenyuClient, method, superPath);
    }

下面查看http使用handleMethod方法:

@Override
protected void handleMethod(final Object bean, final Class<?> clazz,
                            @Nullable final ShenyuSpringMvcClient beanShenyuClient,
                            final Method method, final String superPath) {
    // 在element上查询RequestMapping类型注解
    // 将查询出的多个RequestMapping类型注解属性合并到查询的第一个注解中
    // 多个相同注解合并
    final RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
    //获取对应ShenyuSpringMvcClient注解,查询出多个RequestMapping类型注解属性合并到查询的第一个注解中,多个形同的属性进行合并
    ShenyuSpringMvcClient methodShenyuClient = AnnotatedElementUtils.findMergedAnnotation(method, ShenyuSpringMvcClient.class);
    //判定对应的ShenyuSpringMvcClient是否存在 ,如果不存在,则使用类上面的注解
    methodShenyuClient = Objects.isNull(methodShenyuClient) ? beanShenyuClient : methodShenyuClient;
    // the result of ReflectionUtils#getUniqueDeclaredMethods contains method such as hashCode, wait, toSting
    // add Objects.nonNull(requestMapping) to make sure not register wrong method
    if (Objects.nonNull(methodShenyuClient) && Objects.nonNull(requestMapping)) {
        //构建path信息,构建元数据对象,向shenyu-admin进行注册
        final MetaDataRegisterDTO metaData = buildMetaDataDTO(bean, methodShenyuClient,
                buildApiPath(method, superPath, methodShenyuClient), clazz, method);
        getPublisher().publishEvent(metaData);
        getMetaDataMap().put(method, metaData);
    }
}

这里面需要注意的方法是buildApiPath,先读取方法上的注解ShenyuSpringMvcClient,如果存在就构建;否则从方法的其他注解上获取path信息;完整的path = contextPath(上下文信息)+superPath(类信息)+methodPath(方法信息)

@Override
protected String buildApiPath(final Method method, final String superPath,
                              @NonNull final ShenyuSpringMvcClient methodShenyuClient) {
    //1.获取对应的Contextpath
    String contextPath = getContextPath();
    //如果存在path,就构建
    if (StringUtils.isNotBlank(methodShenyuClient.path())) {
        //完整path=ContextPath+superPath+methodPath
        return pathJoin(contextPath, superPath, methodShenyuClient.path());
    }
    //2.从方法的其他注解上面获取path信息
    final String path = getPathByMethod(method);
    if (StringUtils.isNotBlank(path)) {
        //完整的path=contextPath+superPath+methodPath
        return pathJoin(contextPath, superPath, path);
    }
    return pathJoin(contextPath, superPath);
}

看一下getPathByMethod方法:

private String getPathByMethod(@NonNull final Method method) {
    for (Class<? extends Annotation> mapping : mappingAnnotation) {
        final String pathByAnnotation = getPathByAnnotation(AnnotatedElementUtils.findMergedAnnotation(method, mapping));
        if (StringUtils.isNotBlank(pathByAnnotation)) {
            return pathByAnnotation;
        }
    }
    return null;
}

这个方法是从其他注解上获取 path信息,可以看看getPathByAnnotation:

private String getPathByAnnotation(@Nullable final Annotation annotation) {
    if (Objects.isNull(annotation)) {
        return null;
    }
    final Object value = AnnotationUtils.getValue(annotation, "value");
    if (value instanceof String && StringUtils.isNotBlank((String) value)) {
        return (String) value;
    }
    // Only the first path is supported temporarily
    if (value instanceof String[] && ArrayUtils.isNotEmpty((String[]) value) && StringUtils.isNotBlank(((String[]) value)[0])) {
        return ((String[]) value)[0];
    }
    return null;
}

其他注解包括 在对应的annotation包下面:

  • ShenyuSpringMvcClient
  • PostMapping
  • GetMapping
  • DeleteMapping
  • PutMapping
  • RequestMapping

扫描注解完成后,构建元数据对象,然后将该对象发送到shenyu-admin,即可完成注册。

再看buildMetaDataDTO

private MetaDataRegisterDTO buildMetaDataDTO(@NonNull final ShenyuSpringMvcClient shenyuSpringMvcClient, final String path) {
    return MetaDataRegisterDTO.builder()
            .contextPath(contextPath) // contextPath
            .appName(appName) // appName
            .path(path) // 注册路径,在网关规则匹配时使用
            .pathDesc(shenyuSpringMvcClient.desc()) // 描述信息
            .rpcType(RpcTypeEnum.HTTP.getName()) // divide插件,默认时http类型
            .enabled(shenyuSpringMvcClient.enabled()) // 是否启用规则
            .ruleName(StringUtils.defaultIfBlank(shenyuSpringMvcClient.ruleName(), path))//规则名称
            .registerMetaData(shenyuSpringMvcClient.registerMetaData()) //是否注册元数据信息
            .build();
}

此方法就是构建对应的元数据对象,包括当前注册的规则信息。

注册URI信息

当扫描完成对应的注解信息之后,需要注册对应URI信息。最新版本中仍然是使用SpringMvcClientEventListener方法进行实现的。

首先还是说明初始化,在上面接口扫描中已经说明了对应的初始化,对应的语句是下面几句:

this.appName = props.getProperty(ShenyuClientConstants.APP_NAME);
this.contextPath = Optional.ofNullable(props.getProperty(ShenyuClientConstants.CONTEXT_PATH)).map(UriUtils::repairData).orElse("");
if (StringUtils.isBlank(appName) && StringUtils.isBlank(contextPath)) {
String errorMsg = "client register param must config the appName or contextPath";
    LOG.error(errorMsg);
    throw new ShenyuClientIllegalArgumentException(errorMsg);
}
this.ipAndPort = props.getProperty(ShenyuClientConstants.IP_PORT);
this.host = props.getProperty(ShenyuClientConstants.HOST);
this.port = props.getProperty(ShenyuClientConstants.PORT);
-----
this.isFull = Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.IS_FULL, Boolean.FALSE.toString()));
this.protocol = props.getProperty(ShenyuClientConstants.PROTOCOL, ShenyuClientConstants.HTTP);
this.addPrefixed = Boolean.parseBoolean(props.getProperty(ShenyuClientConstants.ADD_PREFIXED,
        Boolean.FALSE.toString()));

上面是在两个类中进行拿去,其实这里做的事情仍然是读取属性配置。

接下来来到注册URI的逻辑:

@Override
public void onApplicationEvent(@NonNull final ContextRefreshedEvent event) {
    //获取对应的ApplicationContext
    context = event.getApplicationContext();
    //从context获取对应的bean
    Map<String, T> beans = getBeans(context);
    //如果说没有注册对应的URI,返回null
    if (MapUtils.isEmpty(beans)) {
        return;
    }
    //保证该方法执行一次
    if (!registered.compareAndSet(false, true)) {
        return;
    }
    //构建URI数据,并进行注册
    publisher.publishEvent(buildURIRegisterDTO(context, beans));
    //遍历每一个元数据
    beans.forEach(this::handle);
    //获得ApiMoudle注解
    Map<String, Object> apiModules = context.getBeansWithAnnotation(ApiModule.class);
    //处理ApiDoc注解
    apiModules.forEach((k, v) -> handleApiDoc(v, beans));
}

可以查看对应的buildURIRegisterDTO方法:

@Override
protected URIRegisterDTO buildURIRegisterDTO(final ApplicationContext context,
                                             final Map<String, Object> beans) {
    try {
        return URIRegisterDTO.builder()
                .contextPath(getContextPath()) // contextPath
                .appName(getAppName())// appName
                .protocol(protocol)// 服务使用的协议
                .host(super.getHost())//主机
                .port(Integer.valueOf(getPort())) // 端口
                .rpcType(RpcTypeEnum.HTTP.getName())// divide插件,默认注册http类型
                .eventType(EventType.REGISTER)
                .build();
    } catch (ShenyuException e) {
        throw new ShenyuException(e.getMessage() + "please config ${shenyu.client.http.props.port} in xml/yml !");
    }
}

处理注册流程

客户端通过注册中心注册的元数据和URI数据,在shenyu-admin进行处理,负责存储到数据库和同步给shenyu网关。Divide插件的客户端注册处理逻辑在ShenyuClientRegisterDivideServiceImpl中。继承关系如下:

img

  • ShenyuClientRegisterService:客户端注册服务,顶层接口;
  • FallbackShenyuClientRegisterService:注册失败,提供重试操作;
  • AbstractShenyuClientRegisterServiceImpl:抽象类,实现部分公共注册逻辑;
  • AbstractContextPathRegisterService:抽象类,负责注册ContextPath
  • ShenyuClientRegisterDivideServiceImpl:实现Divide插件的注册;

注册服务

org.apache.shenyu.admin.service.register.AbstractShenyuClientRegisterServiceImpl#register()

客户端通过注册中心注册的元数据MetaDataRegisterDTO对象在shenyu-adminregister()方法被接送到。

@Override
public String register(final MetaDataRegisterDTO dto) {
    //handler plugin selector
    //1.注册选择器
    String selectorHandler = selectorHandler(dto);
    String selectorId = selectorService.registerDefault(dto, PluginNameAdapter.rpcTypeAdapter(rpcType()), selectorHandler);
    //handler selector rule
    //2.注册规则
    String ruleHandler = ruleHandler();
    RuleDTO ruleDTO = buildRpcDefaultRuleDTO(selectorId, dto, ruleHandler);
    ruleService.registerDefault(ruleDTO);
    //handler register metadata
    //3.注册元数据
    registerMetadata(dto);
    //handler context path
    //4.注册ContextPath
    String contextPath = dto.getContextPath();
    if (StringUtils.isNotEmpty(contextPath)) {
        registerContextPath(dto);
    }
    return ShenyuResultMessage.SUCCESS;
}

注册选择器

org.apache.shenyu.admin.service.impl.SelectorServiceImpl#registerDefault()

构建contextPath,查找选择器信息是否存在,如果存在就返回id;不存在就创建默认的选择器信息。

@Override
public String registerDefault(final MetaDataRegisterDTO dto, final String pluginName, final String selectorHandler) {
    // 构建contextPath
    String contextPath = ContextPathUtils.buildContextPath(dto.getContextPath(), dto.getAppName());
    // 通过名称查找选择器信息是否存在
    SelectorDO selectorDO = findByNameAndPluginName(contextPath, pluginName);
    if (Objects.isNull(selectorDO)) {
        // 不存在就创建默认的选择器信息
        return registerSelector(contextPath, pluginName, selectorHandler);
    }
    return selectorDO.getId();
}

默认选择器信息

在这里构建默认选择器信息及其条件属性。

//注册选择器
private String registerSelector(final String contextPath, final String pluginName, final String selectorHandler) {
    //构建选择器
    SelectorDTO selectorDTO = buildSelectorDTO(contextPath, pluginMapper.selectByName(pluginName).getId());
    selectorDTO.setHandle(selectorHandler);
    //注册默认选择器
    return registerDefault(selectorDTO);
}
 //构建选择器
private SelectorDTO buildSelectorDTO(final String contextPath, final String pluginId) {
    //构建默认选择器
    SelectorDTO selectorDTO = buildDefaultSelectorDTO(contextPath);
    selectorDTO.setPluginId(pluginId);
     //构建默认选择器的条件属性
    selectorDTO.setSelectorConditions(buildDefaultSelectorConditionDTO(contextPath));
    return selectorDTO;
}

构建默认选择器

private SelectorDTO buildDefaultSelectorDTO(final String name) {
    return SelectorDTO.builder()
            .name(name) // 名称
            .type(SelectorTypeEnum.CUSTOM_FLOW.getCode()) // 默认类型自定义
            .matchMode(MatchModeEnum.AND.getCode()) //默认匹配方式 and
            .enabled(Boolean.TRUE)  //默认启开启
            .loged(Boolean.TRUE)  //默认记录日志
            .continued(Boolean.TRUE) //默认继续后续选择器
            .sort(1) //默认顺序1
            .build();
}

构建默认选择器条件属性

private List<SelectorConditionDTO> buildDefaultSelectorConditionDTO(final String contextPath) {
    SelectorConditionDTO selectorConditionDTO = new SelectorConditionDTO();
    selectorConditionDTO.setParamType(ParamTypeEnum.URI.getName()); // 默认参数类型URI
    selectorConditionDTO.setParamName("/");
    selectorConditionDTO.setOperator(OperatorEnum.MATCH.getAlias()); // 默认匹配策略 match
    selectorConditionDTO.setParamValue(contextPath + AdminConstants.URI_SUFFIX); // 默认值 /contextPath/**
    return Collections.singletonList(selectorConditionDTO);
}

注册默认选择器

@Override
public String registerDefault(final SelectorDTO selectorDTO) {
    //选择器信息
    SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
    //选择器条件属性
    List<SelectorConditionDTO> selectorConditionDTOs = selectorDTO.getSelectorConditions();
    if (StringUtils.isEmpty(selectorDTO.getId())) {
        // 向数据库插入选择器信息
        selectorMapper.insertSelective(selectorDO);
          // 向数据库插入选择器条件属性
        selectorConditionDTOs.forEach(selectorConditionDTO -> {
            selectorConditionDTO.setSelectorId(selectorDO.getId());        
                selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));
        });
    }
    // 发布同步事件,向网关同步选择信息及其条件属性
    publishEvent(selectorDO, selectorConditionDTOs);
    return selectorDO.getId();
}

注册规则

在注册服务的第二步中,开始构建默认规则,然后注册规则。

@Override
    public String register(final MetaDataRegisterDTO dto) {
        //1. 注册选择器
        //......
        
        //2. 注册规则
        // 默认规则处理属性
        String ruleHandler = ruleHandler();
        // 构建默认规则信息
        RuleDTO ruleDTO = buildRpcDefaultRuleDTO(selectorId, dto, ruleHandler);
        // 注册规则
        ruleService.registerDefault(ruleDTO);
        
        //3. 注册元数据
        //......
        
        //4. 注册ContextPath
        //......
        
        return ShenyuResultMessage.SUCCESS;
    }

默认规则处理属性

@Override
protected String ruleHandler() {
    // 默认规则处理属性
    return new DivideRuleHandle().toJson();
}

Divide插件默认规则处理属性

public class DivideRuleHandle implements RuleHandle {
​
    /**
     * 负载均衡:默认随机
     */
    private String loadBalance = LoadBalanceEnum.RANDOM.getName();
​
    /**
     * 重试策略:默认重试当前服务
     */
    private String retryStrategy = RetryEnum.CURRENT.getName();
​
    /**
     * 重试次数:默认3次
     */
    private int retry = 3;
​
    /**
     * 调用超时:默认 3000
     */
    private long timeout = Constants.TIME_OUT;
​
    /**
     * header最大值:10240 byte
     */
    private long headerMaxSize = Constants.HEADER_MAX_SIZE;
​
    /**
     * request最大值:102400 byte
     */
    private long requestMaxSize = Constants.REQUEST_MAX_SIZE;
}

构建默认规则信息

// 构建默认规则信息
private RuleDTO buildRpcDefaultRuleDTO(final String selectorId, final MetaDataRegisterDTO metaDataDTO, final String ruleHandler) {
    return buildRuleDTO(selectorId, ruleHandler, metaDataDTO.getRuleName(), metaDataDTO.getPath());
}
//  构建默认规则信息
private RuleDTO buildRuleDTO(final String selectorId, final String ruleHandler, final String ruleName, final String path) {
    RuleDTO ruleDTO = RuleDTO.builder()
            .selectorId(selectorId) //关联的选择器id
            .name(ruleName) //规则名称
            .matchMode(MatchModeEnum.AND.getCode()) // 默认匹配模式 and
            .enabled(Boolean.TRUE) // 默认开启
            .loged(Boolean.TRUE) //默认记录日志
            .sort(1) //默认顺序 1
            .handle(ruleHandler)
            .build();
    RuleConditionDTO ruleConditionDTO = RuleConditionDTO.builder()
            .paramType(ParamTypeEnum.URI.getName()) // 默认参数类型URI
            .paramName("/")
            .paramValue(path) //参数值path
            .build();
    if (path.indexOf("*") > 1) {
        ruleConditionDTO.setOperator(OperatorEnum.MATCH.getAlias()); //如果path中有*,操作类型则默认为 match
    } else {
        ruleConditionDTO.setOperator(OperatorEnum.EQ.getAlias()); // 否则,默认操作类型 = 
    }
    ruleDTO.setRuleConditions(Collections.singletonList(ruleConditionDTO));
    return ruleDTO;
}

org.apache.shenyu.admin.service.impl.RuleServiceImpl#registerDefault()

注册规则:向数据库插入记录,并向网关发布事件,进行数据同步。

@Override
public String registerDefault(final RuleDTO ruleDTO) {
    RuleDO exist = ruleMapper.findBySelectorIdAndName(ruleDTO.getSelectorId(), ruleDTO.getName());
    if (Objects.nonNull(exist)) {
        return "";
    }
​
    RuleDO ruleDO = RuleDO.buildRuleDO(ruleDTO);
    List<RuleConditionDTO> ruleConditions = ruleDTO.getRuleConditions();
    if (StringUtils.isEmpty(ruleDTO.getId())) {
        // 向数据库插入规则信息
        ruleMapper.insertSelective(ruleDO);
        //向数据库插入规则体条件属性
        ruleConditions.forEach(ruleConditionDTO -> {
            ruleConditionDTO.setRuleId(ruleDO.getId());            
            ruleConditionMapper.insertSelective(RuleConditionDO.buildRuleConditionDO(ruleConditionDTO));
        });
    }
    // 向网关发布事件,进行数据同步
    publishEvent(ruleDO, ruleConditions);
    return ruleDO.getId();
}
​

注册元数据

@Override
public String register(final MetaDataRegisterDTO dto) {
    //1. 注册选择器
    //......//2. 注册规则
    //......//3. 注册元数据
    registerMetadata(dto);
​
    //4. 注册ContextPath
    //......return ShenyuResultMessage.SUCCESS;
}

org.apache.shenyu.admin.service.register.ShenyuClientRegisterDivideServiceImpl#registerMetadata()

插入或更新元数据,然后发布同步事件到网关。

@Override
protected void registerMetadata(final MetaDataRegisterDTO dto) {
    if (dto.isRegisterMetaData()) { // 如果注册元数据
        // 获取metaDataService
        MetaDataService metaDataService = getMetaDataService();
        // 元数据是否存在
        MetaDataDO exist = metaDataService.findByPath(dto.getPath());
        // 插入或更新元数据
        metaDataService.saveOrUpdateMetaData(exist, dto);
    }
}

@Override
public void saveOrUpdateMetaData(final MetaDataDO exist, final MetaDataRegisterDTO metaDataDTO) {
    DataEventTypeEnum eventType;
    // 数据类型转换 DTO->DO
    MetaDataDO metaDataDO = MetaDataTransfer.INSTANCE.mapRegisterDTOToEntity(metaDataDTO);
    // 插入数据
    if (Objects.isNull(exist)) {
        Timestamp currentTime = new Timestamp(System.currentTimeMillis());
        metaDataDO.setId(UUIDUtils.getInstance().generateShortUuid());
        metaDataDO.setDateCreated(currentTime);
        metaDataDO.setDateUpdated(currentTime);
        metaDataMapper.insert(metaDataDO);
        eventType = DataEventTypeEnum.CREATE;
    } else {
        // 更新数据
        metaDataDO.setId(exist.getId());
        metaDataMapper.update(metaDataDO);
        eventType = DataEventTypeEnum.UPDATE;
    }
    // 发布同步事件到网关
    eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, eventType,
            Collections.singletonList(MetaDataTransfer.INSTANCE.mapToData(metaDataDO))));
}

注册ContextPath

@Override
public String register(final MetaDataRegisterDTO dto) {
    //1. 注册选择器
    //......//2. 注册规则
    //......//3. 注册元数据
    //......//4. 注册ContextPath
    String contextPath = dto.getContextPath();
    if (StringUtils.isNotEmpty(contextPath)) {
        registerContextPath(dto);
    }
    return ShenyuResultMessage.SUCCESS;
}

org.apache.shenyu.admin.service.register.AbstractContextPathRegisterService#registerContextPath()

@Override
public void registerContextPath(final MetaDataRegisterDTO dto) {
    // 设置选择器的contextPath
    String contextPathSelectorId = getSelectorService().registerDefault(dto, PluginEnum.CONTEXT_PATH.getName(), "");
    ContextMappingRuleHandle handle = new ContextMappingRuleHandle();
    handle.setContextPath(PathUtils.decoratorContextPath(dto.getContextPath()));
    // 设置规则的contextPath
    getRuleService().registerDefault(buildContextPathDefaultRuleDTO(contextPathSelectorId, dto, handle.toJson()));
}

注册URI

org.apache.shenyu.admin.service.register.FallbackShenyuClientRegisterService#registerURI()

服务端收到客户端注册的URI信息后,进行处理。

@Override
public String registerURI(final String selectorName, final List<URIRegisterDTO> uriList) {
    String result;
    String key = key(selectorName);
    try {
        this.removeFallBack(key);
        // 注册URI
        result = this.doRegisterURI(selectorName, uriList);
        logger.info("Register success: {},{}", selectorName, uriList);
    } catch (Exception ex) {
        logger.warn("Register exception: cause:{}", ex.getMessage());
        result = "";
        // 注册失败后,进行重试
        this.addFallback(key, new FallbackHolder(selectorName, uriList));
    }
    return result;
}

org.apache.shenyu.admin.service.register.AbstractShenyuClientRegisterServiceImpl#doRegisterURI()

从客户端注册的URI中获取有效的URI,更新对应的选择器handle属性,向网关发送选择器更新事件。

@Override
public String doRegisterURI(final String selectorName, final List<URIRegisterDTO> uriList) {
    //参数检查
    if (CollectionUtils.isEmpty(uriList)) {
        return "";
    }
    //获取选择器信息
    SelectorDO selectorDO = selectorService.findByNameAndPluginName(selectorName, PluginNameAdapter.rpcTypeAdapter(rpcType()));
    if (Objects.isNull(selectorDO)) {
        throw new ShenyuException("doRegister Failed to execute,wait to retry.");
    }
    // 获取有效的URI
    List<URIRegisterDTO> validUriList = uriList.stream().filter(dto -> Objects.nonNull(dto.getPort()) && StringUtils.isNotBlank(dto.getHost())).collect(Collectors.toList());
    // 构建选择器的handle属性
    String handler = buildHandle(validUriList, selectorDO);
    if (handler != null) {
        selectorDO.setHandle(handler);
        SelectorData selectorData = selectorService.buildByName(selectorName, PluginNameAdapter.rpcTypeAdapter(rpcType()));
        selectorData.setHandle(handler);
        // 向数据库更新选择器的handle属性
        selectorService.updateSelective(selectorDO);
        // 向网关发送选择器更新事件
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE, Collections.singletonList(selectorData)));
    }
    return ShenyuResultMessage.SUCCESS;
}

引用官网上面一张图,总结整个流程(注意,因为类的变换,读取接口信息构造元数据以及读取配置信息,构造URI数据都在一个类中),但是具体的流程没有改变。

img

当然,这个图片中少了一个很重要的内容,网关中的数据如何同步到本地缓存中。这个需要专门写一篇文章。

下面在使用一个博客中的内容作为整个过程的总结:

img

TODO:重新画这个图,这个图中存在一些问题,因为部分的类更改了,上文中进行了具体的分析。

img

总结

本文主要是说明了服务注册的过程,但是里面还有很多问题没有进行具体说明:

  1. 服务是如何进行同步的
  2. 服务在进行调用的时候流程是什么

本系列是自己对shenyu进行学习的系列,参考了许多博客以及官网上面的内容,完全是班门弄斧,放在自己的博客上面,如果存在错误或者侵权,请在下面评论。

参考