soul网关源码分析之发布接口到网关

soul网关源码分析之发布接口到网关

目标

  • Http API发布到soul网关源码分析
  • Dubbo API发布到soul网关源码分析
  • 总结

概述

    业务系统在接口方法上加类似于@SoulSpringMvcClient``@SoulDubboClient注解,就能够将需要被soul网关代理的接口发布到soul-admin和soul网关,本篇文章主要分析soul网关是如何做到的自动发布API。

Http API发布到soul网关源码分析

  • pom依赖及yml配置
    <dependency>
        <groupId>org.dromara</groupId>
        <artifactId>soul-spring-boot-starter-client-springmvc</artifactId>
        <version>${soul-version}</version>
    </dependency>  
soul:
  http:
    adminUrl: http://localhost:9095
    port: 8086
    contextPath: /soul
    appName: http
    full: true
  • API发布到网关流程分析

    发布网关的client配置在soul-spring-boot-starter-client-springmvc包中,通过soul-spring-boot-starter-client-springmvc包的spring.factories的可以知道配置是org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration,配置的代码如下:

@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);
    }
    
    /**
     * Context register listener context register listener.
     *
     * @param soulSpringMvcConfig the soul spring mvc config
     * @return the context register listener
     */
    @Bean
    public ContextRegisterListener contextRegisterListener(final SoulSpringMvcConfig soulSpringMvcConfig) {
        return new ContextRegisterListener(soulSpringMvcConfig);
    }
    
    /**
     * Soul http config soul http config.
     *
     * @return the soul http config
     */
    @Bean
    @ConfigurationProperties(prefix = "soul.http")
    public SoulSpringMvcConfig soulHttpConfig() {
        return new SoulSpringMvcConfig();
    }
}

    下面逐一看一下每个Bean的源码

    • soulHttpConfig()源码分析

    soulHttpConfig()yml配置文件中读取参数然后实例化了SoulSpringMvcConfig

    • SpringMvcClientBeanPostProcessor源码分析

    springHttpClientBeanPostProcessor方法初始化SpringMvcClientBeanPostProcessorSpringMvcClientBeanPostProcessor类实现了BeanPostProcessor,这是Spring的接口,作用是在所有的Bean对象实例化和依赖注入完毕后,在显示的调用初始化方法的前后添加我们自己的逻辑,通常该接口称为后置处理器,以下是BeanPostProcessor代码:

public interface BeanPostProcessor {
    // Bean初始化前
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    // Bean初始化后
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

    在SpringMvcClientBeanPostProcessor类中,重写的是postProcessAfterInitialization方法,意味着当所有的Bean初始化和依赖注入完成后要完成一些操作,操作的步骤是:

  1. 从yml中读取full配置内容,如果是true直接返回bean不做任何操作
  2. 如果是false(不代理整个服务),那就处理所有初始化完成的Bean,处理的步骤是
    • 通过反射过去每个初始化完成的Bean的Controller``RestController``RequestMapping注解
    • 如果获取到以上注解的任意一个那就从class上获取SoulSpringMvcClient注解
    • 如果从class上获取到了注解,判断注解的path内容是不是末尾有大于一个*,如果有,代表改类的所有方法都被代理,然后注册到网关
    • 如果class上没有SoulSpringMvcClient注解,那用通过反射从该类的方法上找,找到该注解就注册到网关
    • 注册到网关的内容有contextPath,网关代理后的请求路径path,appName,host,port,描述pathDesc,rpc的类型,开关,规则名称ruleName,元数据
  3. 注册的RegisterUtils工具类就是OkHttp实现的HttpClient

在这里插入图片描述

构建注册数据的代码

在这里插入图片描述

    • ContextRegisterListener源码分析

    contextRegisterListener方法初始化了ContextRegisterListenerContextRegisterListener实现了ApplicationListener并且监听的是ContextRefreshedEvent事件,先解释下ApplicationListener是干什么的。
    首先,这其实是ApplicationContext的事件机制,事件机制是基于观察者的设计模式实现的,ContextRegisterListener监听的是ContextRefreshedEvent事件,它是spring的内置事件,意思是,当所有的bean都初始化完成并被成功装载后会触发该事件,触发该事件后的处理操作在重写的onApplicationEvent方法中,代码的意思就是如果full设置了true,代表整个服务都被代理,直接注册注册到网关即可。
在这里插入图片描述

    • Http API发布到soul网关源码分析总结

    Http API发布到soul网关,使用的是spring的BeanPostProcessor后置处理和ApplicationListener<ContextRefreshedEvent>事件机制完成;SpringMvcClientBeanPostProcessor实现BeanPostProcessor当所有的Bean实例初始化和依赖注入完成后,扫描所有实例化完成的Bean的注册网关注解SoulSpringMvcClient完成注册API到网关,SpringMvcClientBeanPostProcessor注册的是指定的API也就是说full配置为false;如果配置为true是由ContextRegisterListener完成的API注册,发布到soul-admin成功后,soul-admin会与soul网关进行数据同步刷新可以参考上篇文章Soul网关源码分析之soul-admin与soul-gateway数据同步
soul网关源码分析之soul-gateway缓存刷新

Dubbo API发布到soul网关源码分析

  • API发布到网关流程分析

    发布API到网关的client配置在soul-spring-boot-starter-client-alibaba-dubbo包中,通过soul-spring-boot-starter-client-alibaba-dubbo包的spring.factories的可以知道配置是org.dromara.soul.springboot.starter.client.alibaba.dubbo.SoulAlibabaDubboClientConfiguration,配置的代码如下:

@Configuration
public class SoulAlibabaDubboClientConfiguration {
    
    /**
     * Alibaba dubbo service bean post processor alibaba dubbo service bean post processor.
     *
     * @param dubboConfig the dubbo config
     * @return the alibaba dubbo service bean post processor
     */
    @Bean
    public AlibabaDubboServiceBeanPostProcessor alibabaDubboServiceBeanPostProcessor(final DubboConfig dubboConfig) {
        return new AlibabaDubboServiceBeanPostProcessor(dubboConfig);
    }
    
    /**
     * Dubbo config dubbo config.
     *
     * @return the dubbo config
     */
    @Bean
    @ConfigurationProperties(prefix = "soul.dubbo")
    public DubboConfig dubboConfig() {
        return new DubboConfig();
    }
}

下面逐一看一下每个Bean的源码

    • dubboConfig()方法

yml配置文件中读取参数,实例化DubboConfig

    • AlibabaDubboServiceBeanPostProcessor源码分析

    AlibabaDubboServiceBeanPostProcessor实现了ApplicationListener并监听ContextRefreshedEvent事件,之前说过ApplicationListener是spring的ApplicationContext事件机制,该机制基于观察者模式实现,当所有的bean初始化完成并被成功的装载后就会触发这个事件,触发该事件后的处理操作在重写的onApplicationEvent方法中,这个方法总的逻辑如下,dubbo服务启动后会将spring的上下文,设置到dubbo的API对象中(ServiceBean的setApplicationContext),程序在后续的启动过程中会发布API到zk注册中心,当所有的bean初始化并装载完成,执行下面的方法,下面方法的逻辑是,从上下文中获取parent,如果是空的那就从spring上下文中获取ServiceBeanServiceBean就是dubbo发布的API,然后遍历发布的所有发布的dubbo API,异步的进行处理,处理的逻辑就是将dubbo API发布到soul网关。

    // 处理ContextRefreshedEvent事件
    @Override
    public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
        if (Objects.nonNull(contextRefreshedEvent.getApplicationContext().getParent())) {
            return;
        }
        // Fix bug(https://github.com/dromara/soul/issues/415), upload dubbo metadata on ContextRefreshedEvent
        Map<String, ServiceBean> serviceBean = contextRefreshedEvent.getApplicationContext().getBeansOfType(ServiceBean.class);
        for (Map.Entry<String, ServiceBean> entry : serviceBean.entrySet()) {
            executorService.execute(() -> handler(entry.getValue()));
        }
    }

    以下是将dubbo API发布到soul网关的流程,serviceBean.getRef().getClass()获取到dubbo API的classclass是dubbo的代理类也就是已经创建的dubbo API的代理实现,他的样子是com.account.impl.AccountServiceImpl$$EnhancerBySpringCGLIB$$57341b22,然后使用isCglibProxyClass判断是不是使用CGlib增强生成的代理类,是的话就获取dubbo API实现类的className(com.account.impl.AccountServiceImpl),然后反射生成这个类,然后再通过反射拿到这个类所有的方法,遍历这些方法,扫描有无SoulDubboClient注解,如果有的话就构造发送到soul-admin的rpc数据和元数据MetaDataDTO,然后使用OkHttp发到soul-admin。

    // 处理发布dubbo API 到soul-admin
    private void handler(final ServiceBean<?> serviceBean) {
        Class<?> clazz = serviceBean.getRef().getClass();
        if (ClassUtils.isCglibProxyClass(clazz)) {
            String superClassName = clazz.getGenericSuperclass().getTypeName();
            try {
                clazz = Class.forName(superClassName);
            } catch (ClassNotFoundException e) {
                log.error(String.format("class not found: %s", superClassName));
                return;
            }
        }
        final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
        for (Method method : methods) {
            SoulDubboClient soulDubboClient = method.getAnnotation(SoulDubboClient.class);
            if (Objects.nonNull(soulDubboClient)) {
                RegisterUtils.doRegister(buildJsonParams(serviceBean, soulDubboClient, method), url, RpcTypeEnum.DUBBO);
            }
        }
    }
    // 构造注册数据
    private String buildJsonParams(final ServiceBean<?> serviceBean, final SoulDubboClient soulDubboClient, final Method method) {
        String appName = dubboConfig.getAppName();
        if (StringUtils.isEmpty(appName)) {
            appName = serviceBean.getApplication().getName();
        }
        String path = dubboConfig.getContextPath() + soulDubboClient.path();
        String desc = soulDubboClient.desc();
        String serviceName = serviceBean.getInterface();
        String configRuleName = soulDubboClient.ruleName();
        String ruleName = ("".equals(configRuleName)) ? path : configRuleName;
        String methodName = method.getName();
        Class<?>[] parameterTypesClazz = method.getParameterTypes();
        String parameterTypes = Arrays.stream(parameterTypesClazz).map(Class::getName)
                .collect(Collectors.joining(","));
        MetaDataDTO metaDataDTO = MetaDataDTO.builder()
                .appName(appName)
                .serviceName(serviceName)
                .methodName(methodName)
                .contextPath(dubboConfig.getContextPath())
                .path(path)
                .ruleName(ruleName)
                .pathDesc(desc)
                .parameterTypes(parameterTypes)
                .rpcExt(buildRpcExt(serviceBean))
                .rpcType("dubbo")
                .enabled(soulDubboClient.enabled())
                .build();
        return OkHttpTools.getInstance().getGson().toJson(metaDataDTO);

    }
    // 构造rpc数据
    private String buildRpcExt(final ServiceBean<?> serviceBean) {
        MetaDataDTO.RpcExt build = MetaDataDTO.RpcExt.builder()
                .group(StringUtils.isNotEmpty(serviceBean.getGroup()) ? serviceBean.getGroup() : "")
                .version(StringUtils.isNotEmpty(serviceBean.getVersion()) ? serviceBean.getVersion() : "")
                .loadbalance(StringUtils.isNotEmpty(serviceBean.getLoadbalance()) ? serviceBean.getLoadbalance() : Constants.DEFAULT_LOADBALANCE)
                .retries(Objects.isNull(serviceBean.getRetries()) ? Constants.DEFAULT_RETRIES : serviceBean.getRetries())
                .timeout(Objects.isNull(serviceBean.getTimeout()) ? Constants.DEFAULT_CONNECT_TIMEOUT : serviceBean.getTimeout())
                .url("")
                .build();
        return OkHttpTools.getInstance().getGson().toJson(build);

    }
    • Dubbo API发布到soul网关源码分析总结

    Dubbo API发布到soul网关的方法其实和Http API大同小异,都是通过spring的事件监听机制,说白了就是增强的处理,不同的就在于处理注册数据的细节,Dubbo API发布的除了soul网关的规则、元数据等信息,还发布了dubbo自身的rpc数据,soul-admin在接收到注册数据后与soul网关数据的刷新过程实际上都是一样的Soul网关源码分析之soul-admin与soul-gateway数据同步
soul网关源码分析之soul-gateway缓存刷新

总结

    Http API和Dubbo API发布到soul网关流程总结起来,我理解的就是,在spring初始化并装载完所有的bean后,通过后置增强或者事件监听来处理带有soul网关Client注解的bean,将其组装成soul网关数据同步到soul-admin。

猜你喜欢

转载自blog.csdn.net/qq_31279701/article/details/112855269