二、soul源码学习-http项目本地运行

soul单机部署简单架构

image.png

遵循 https://dromara.org/zh-cn/docs/soul/induction.html 教程,尝试本地搭建一个soul网关

一、启动soul-admin服务

soul-admin是soul网关的控制中心,负责网关的管理和配置,并同步给网关服务

  1. 上一节我们已经本地编译通过了整个soul项目的代码。我们可以直接通过idea引入整个soul项目


    image.png
  2. 修改soul-admin项目中resources/application.yml的数据库配置文件


    image.png
  3. 运行SoulAdminBootstrap
  4. 启动完成后,使用浏览器访问http://127.0.0.1:9095地址,进入到登录页面


    image.png
  5. 输入默认用户名密码 admin , 123456。进入首页


    image.png

到此 soul-admin的启动已经完成,至于具体的配置项我们之后在继续深入研究

二、搭建自己的网关

示例代码:https://github.com/wyc192273/soul-learn-project/tree/main/soul-bootstrap-test

  1. 首先使用idea创建一个spring-boot项目,可以参考soul-bootstrap项目
  2. 在pom引入如下配置
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

        <!--soul gateway start-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-gateway</artifactId>
            <version>${soul.last.version}</version>
        </dependency>

        <!--soul data sync start use websocket-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-sync-data-websocket</artifactId>
            <version>${soul.last.version}</version>
        </dependency>
  1. 在application.yaml文件,加上如下配置
spring:
   main:
     allow-bean-definition-overriding: true

management:
  health:
    defaults:
      enabled: false
soul :
    sync:
        websocket :
             # 设置你的admin的地址
             urls: ws://localhost:9095/websocket 
  1. 启动项目后,网关便启动成功了
2021-01-15 22:14:11.116  INFO 68511 --- [           main] com.soul.bootstrap.Application           : Starting Application on kuaikandeMacBook-Pro-8.local with PID 68511 (/Users/kuaikan/work/open_source/soul-learn-project/soul-bootstrap-test/target/classes started by kuaikan in /Users/kuaikan/work/open_source/website)
2021-01-15 22:14:11.117  INFO 68511 --- [           main] com.soul.bootstrap.Application           : No active profile set, falling back to default profiles: default
2021-01-15 22:14:13.791  INFO 68511 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[global] [org.dromara.soul.plugin.global.GlobalPlugin]
2021-01-15 22:14:13.963  INFO 68511 --- [           main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
2021-01-15 22:14:14.107  INFO 68511 --- [           main] o.d.s.p.s.d.w.WebsocketSyncDataService   : websocket connection is successful.....
2021-01-15 22:14:14.197  INFO 68511 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-01-15 22:14:14.448  INFO 68511 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2021-01-15 22:14:14.452  INFO 68511 --- [           main] com.soul.bootstrap.Application           : Started Application in 4.334 seconds (JVM running for 5.909)

三、网关接入Spring Boot 项目

示例代码:
https://github.com/wyc192273/soul-learn-project/tree/main/spring-boot-test
https://github.com/wyc192273/soul-learn-project/tree/main/spring-boot-test2
我们从最简单的spring-boot项目入手

image.png

通过前面两步我们已经部署了我们的soul-admin(soul网关控制台),soul-bootstrap(网关),接下来我要接入我们的spring-boot项目

在<u>网关项目spring-bootstrap</u>中,引入pom依赖,来支持http代理

这里要注意,下面的配置是在soul-bootstrap网关项目中!!!不要加到我们自己的spring-boot项目中

  • pom文件引入相关依赖
 <!--if you use http proxy start this-->
        <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-plugin-divide</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-bootstrap
2021-01-15 22:41:43.064  INFO 68666 --- [           main] com.soul.bootstrap.Application           : Starting Application on kuaikandeMacBook-Pro-8.local with PID 68666 (/Users/kuaikan/work/open_source/soul-learn-project/soul-bootstrap-test/target/classes started by kuaikan in /Users/kuaikan/work/open_source/website)
2021-01-15 22:41:43.065  INFO 68666 --- [           main] com.soul.bootstrap.Application           : No active profile set, falling back to default profiles: default
2021-01-15 22:41:44.656  INFO 68666 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[global] [org.dromara.soul.plugin.global.GlobalPlugin]
2021-01-15 22:41:44.657  INFO 68666 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[divide] [org.dromara.soul.plugin.divide.DividePlugin]
2021-01-15 22:41:44.657  INFO 68666 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[webClient] [org.dromara.soul.plugin.httpclient.WebClientPlugin]
2021-01-15 22:41:44.657  INFO 68666 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[divide] [org.dromara.soul.plugin.divide.websocket.WebSocketPlugin]
2021-01-15 22:41:44.657  INFO 68666 --- [           main] o.d.s.w.configuration.SoulConfiguration  : load plugin:[response] [org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin]
2021-01-15 22:41:44.793  INFO 68666 --- [           main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
2021-01-15 22:41:44.842  INFO 68666 --- [           main] o.d.s.p.s.d.w.WebsocketSyncDataService   : websocket connection is successful.....
2021-01-15 22:41:44.929  INFO 68666 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-01-15 22:41:45.196  INFO 68666 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2021-01-15 22:41:45.208  INFO 68666 --- [           main] com.soul.bootstrap.Application           : Started Application in 2.781 seconds (JVM running for 3.73)

会发现日志中增加了额外的几个插件plugin,其中就包括下面要说的divide插件

  • 设置divide插件
    需要设置divide插件为开启。该插件实现HTTP正向代理的功能,所有HTTP类型相关的请求,都由它进行负载均衡的调用
    使用浏览器访问http://127.0.0.1:9095/#/system/plugin,进入「系统管理-插件管理菜单」,保证divide插件是开启状态
image.png
  • 默认情况下就是开启状态
  • 接下来搭建我们自己的Spring Boot项目,并简单支持http请求
image.png
  • 在pom引入如下依赖。
<dependency>
  <!-- Soul对SpringMVC的集成支持 -->
    <groupId>org.dromara</groupId>
    <artifactId>soul-spring-boot-starter-client-springmvc</artifactId>
    <version>${last.version}</version>
</dependency>
  • 修改application.yaml文件,添加如下配置项
soul:
  # Soul 针对 SpringMVC 的配置项,对应 SoulHttpConfig 配置类
  http:
    admin-url: http://127.0.0.1:9095 # Soul Admin 地址
    context-path: /my-api # 设置在 Soul 网关的路由前缀,例如说 /order、/product 等等。
                               # 后续,网关会根据该 context-path 来进行路由
    app-name: my-service # 应用名。未配置情况下,默认使用 `spring.application.name` 配置项
    port: 8081 #你本项目的启动端口
    full: false   # 设置true 代表代理你的整个服务,false表示代理你其中某几个controller,如果设置了false,我们需要在自己的要拦截controller类或方法上手动添加@SoulSpringMvcClient注解
  • 添加@SoulSpringMvcClient注解。这里我修改在UserController类上
@RestController
@RequestMapping("user")
@SoulSpringMvcClient(path = "/user")
public class UserController {

    @GetMapping("find_by_id")
    public Object findById(@RequestParam("id") int id) {
        User user = new User();
        user.setId(id);
        user.setName("name_" + id);
        return user;
    }

}
  • 启动项目, 发现下面这些信息和soul有关
// 暂时忽略
2021-01-15 23:13:27.625  INFO 68877 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration' of type [org.dromara.soul.springboot.starter.client.springmvc.SoulSpringMvcClientConfiguration$$EnhancerBySpringCGLIB$$23a5833d] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-01-15 23:13:27.664  INFO 68877 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'soulHttpConfig' of type [org.dromara.soul.client.springmvc.config.SoulSpringMvcConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

//同步配置到soul-admin
2021-01-15 23:22:56.723  INFO 68904 --- [pool-1-thread-1] o.d.s.client.common.utils.RegisterUtils  : http client register success: {"appName":"my-service","context":"/my-api","path":"/my-api/user/*","pathDesc":"","rpcType":"http","host":"192.168.1.7","port":8081,"ruleName":"/my-api/user/*","enabled":true,"registerMetaData":false} 

image.png

至于插件的具体作用,我们后面章节在讨论

image.png

我们的网关并接入Spring Boot项目已经完成

问题

  1. 讲@SoulSpringMvcClient注解到类上,path没有写*导致没有注册到selector上

通过查看@SoulSpringMvcClient发现,只有SpringMvcClientBeanPostProcessor有用到该注解。那么我们跳到SpringMvcClientBeanPostProcessor类里看下具体逻辑

  1. 类SpringMvcClientBeanPostProcessor重载了BeanPostProcessor的postProcessAfterInitialization方法
class SpringMvcClientBeanPostProcessor implements BeanPostProcessor 
  1. 构造方法,会将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. 主要是校验了contextPath和adminUrl 和port不可为空,这些是我们之前在application.yaml里面的配置
// org.dromara.soul.client.springmvc.utils.ValidateUtils#validate
  /**
     * 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);
    }
  1. 重点是 postProcessAfterInitialization 的实现
 @Override
    public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
      //如果我们配置了全部代理,则直接返回bean
        if (soulSpringMvcConfig.isFull()) {
            return bean;
        }
      //这里主要是想拦截所有的Controller类
        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则代表是SpringMVC的Controller
        if (controller != null || restController != null || requestMapping != null) {
          //看类上是否有SoulSpringMvcClient注解
            SoulSpringMvcClient clazzAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), SoulSpringMvcClient.class);
            String prePath = "";
          //如果有SoulSpringMvcClient注解
            if (Objects.nonNull(clazzAnnotation)) {
              //如果path中存在*号
                if (clazzAnnotation.path().indexOf("*") > 1) {
                  //则finalPrePath最后的前缀更新为当前注解中配置的path
                    String finalPrePath = prePath;
                  //注册相关配置
                    executorService.execute(() -> RegisterUtils.doRegister(buildJsonParams(clazzAnnotation, finalPrePath), url,
                            RpcTypeEnum.HTTP));
                    return bean;
                }
              //如果path中不能存在*,则讲当前注解的path更新为prePath
                prePath = clazzAnnotation.path();
            }
          //获取当前controller的所有方法
            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;
    }
  1. 我们在看下buildJosnParams具体干了什么
private String buildJsonParams(final SoulSpringMvcClient soulSpringMvcClient, final String prePath) {
        String contextPath = soulSpringMvcConfig.getContextPath();
        String appName = soulSpringMvcConfig.getAppName();
        Integer port = soulSpringMvcConfig.getPort();
        //构造paht = application.yaml中的contextPath + 参数的prePath + 注解的path 
        String path = contextPath + prePath + soulSpringMvcClient.path();
        String desc = soulSpringMvcClient.desc();
        String configHost = soulSpringMvcConfig.getHost();
        String host = StringUtils.isBlank(configHost) ? IpUtils.getHost() : configHost;
        String configRuleName = soulSpringMvcClient.ruleName();
        String ruleName = StringUtils.isBlank(configRuleName) ? path : configRuleName;
        //构造 SpringMVC注册的 model
        SpringMvcRegisterDTO registerDTO = SpringMvcRegisterDTO.builder()
                .context(contextPath)
                .host(host)
                .port(port)
                .appName(appName)
                .path(path)
                .pathDesc(desc)
                .rpcType(soulSpringMvcClient.rpcType())
                .enabled(soulSpringMvcClient.enabled())
                .ruleName(ruleName)
                .registerMetaData(soulSpringMvcClient.registerMetaData())
                .build();
        return OkHttpTools.getInstance().getGson().toJson(registerDTO);
    }
  1. 构造了SpringMVC注册json,然后在通过doRegister方法注册相关信息到soul-admin
//这里就是简单的通过OkHttp工具发送post请求注册到soul-admin
    public static void doRegister(final String json, final String url, final RpcTypeEnum rpcTypeEnum) {
        try {
            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);
        }
    }
  1. 这里注册的url是通过构造方法中构造的。这里会看到就是我们application.yaml配置的adminUrl加上一个固定的请求
url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
  1. 到这里我们就将SpringMVC注册的逻辑梳理清晰了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容