美文网首页
二、soul源码学习-http项目本地运行

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

作者: 风洛洛 | 来源:发表于2021-01-17 20:39 被阅读0次

    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注册的逻辑梳理清晰了

    相关文章

      网友评论

          本文标题:二、soul源码学习-http项目本地运行

          本文链接:https://www.haomeiwen.com/subject/ziciaktx.html