一. 问题背景

技术栈:SpringBoot+SpringCloud
项目结构组织:项目由多个Module构成,而每个Module又由api模块、model模块、web模块构成

需求:现在需要将A项目(生产者,或者叫服务提供者)的web模块中的某些接口暴露出去,要求在api模块中定义。使得B项目(消费者,或者叫客户端)能远程调用A项目暴露的接口

备注:笔者对SpringCloud只有了解并未实践过,导致实现这个需求时踩了不少坑,本博客适合初次实操SpringCloud或者如何暴露接口的小白。

解决踩坑过程中觉得不错的博客:【SpringCloud】 - Feign 踩坑记录:404 ,调用不成功 , 接口定义规范 等问题记录

二. 解决方案

这里先给出代码实现以及注意事项,后面有时间再补充微服务相关的知识。

2.1 实现生产者中的接口

  • 首先实现生产者中的接口,在A项目的web模块中,代码如下:
@Api(value = "字典定义", tags = " 字典定义")
@RestController
@RequestMapping(value = "/wcenter/dict/define")
public class WcenterDictDefineRest {

 /**
   * 获取树状字典
   **/
  @ApiOperation(value = "获取树状字典")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "codes", value = "字典编码", paramType = "query", dataType = "String[]"),
  })
  @GetMapping(value = "/tree")
  public ResponsePacket<ResponseTreeEntityDto> tree(HttpServletRequest request, String[] codes) {
    //...业务逻辑
    return xxx;
  }

}

注意:上面那个类,必须加@RestController注解,否则后面调用api模块暴露的接口会出现问题。

2.2 暴露接口

  • 首先要引入Feign的依赖,Feign依赖是必须引入的,其余依赖根据自己的接口需要引入其他依赖。如下:
 <!-- Feign Form Spring -->
    <dependency>
      <groupId>io.github.openfeign.form</groupId>
      <artifactId>feign-form-spring</artifactId>
      <version>3.8.0</version>
    </dependency>

    <!-- Feign Form -->
    <dependency>
      <groupId>io.github.openfeign.form</groupId>
      <artifactId>feign-form</artifactId>
      <version>3.8.0</version>
    </dependency>

    <!-- Feign Core-->
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-core</artifactId>
      <version>10.10.1</version>
    </dependency>

    <!-- Feign Hystrix -->
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-hystrix</artifactId>
      <version>10.10.1</version>
    </dependency>

    <!-- Feign Slf4j -->
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-slf4j</artifactId>
      <version>10.10.1</version>
    </dependency>
  • 暴露接口,在A项目的api模块中,代码如下:
@Api("数据字典远程服务接口")
@FeignClient(value = "接口的实现方法所在项目的微服务名", path = "web以及请求路径")
public interface DictFeign {

  @ApiOperation(value = "获取树状字典")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "codes", value = "字典编码", paramType = "query", dataType = "String[]"),
  })
  @GetMapping(value = "/tree")
  ResponsePacket<ResponseTreeEntityDto> tree(@RequestParam("request") HttpServletRequest request, @RequestParam("codes") String[] codes);

}

注意:

  1. 必须加@FeignClient声明
  2. 其他 value属性的值是yml文件中spring.application.name的值,这里建议用全局变量来填写,方便管理更多的微服务
  3. 如果yml文件中配置了server.servlet.context-path,必须配上path属性。否则后面调用这个接口会出现404问题
  4. 关于方法的mapping注解,有人说Feign不支持 GetMapping,笔者测试过 是支持的。这应该与某些依赖的版本有关
  5. 方法参数必须加@RequestParam注解(否则,后面调用该接口会报too many bad parameter错误),若是POST请求方式,需要根据情况加@RequestBody注解

2.3 远程调用

  • 需要在B项目中引入A项目的api模块的依赖,例子如下:
<dependency>
  <groupId>xxx.xxx</groupId>
  <artifactId>business-wcenter-api</artifactId>
  <version>2.0.0</version>
</dependency>
  • B项目的启动类必须加上@EnableFeignClients注解,还必须要加上basePackages的属性,他的值是标注了@FeignClient注解的接口的包路径,如下:
@EnableFeignClients(basePackages = "xx.xx.xx")
@SpringCloudApplication
public class CriterionServerApplication {

}

2.4 SpringBoot整合测试

引入SpringBoot的测试依赖,如下:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
</dependency>
  • 代码如下:
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(
    classes = 启动类.class
    )
public class DictFeignTest {

  @Autowired
  DictFeign dictFeign;

  /**
   * 测试 远程调用数据字典
   */
  @Test
  public void testRpcDictFeign(){
    ResponsePacket<ResponseTreeEntityDto> tree = dictFeign.tree(null, null);
    log.info("The dict tree is: {}", tree);
  }
}

2.5 开始测试

这种暴露接口的测试,必须先启动生产者(服务提供者),再启动消费者(客户端)。

我们先启动A项目,再启动B项目,运行上面的测试方法,测试成功。

三. 回调处理

前面讲述了远程调用的简单版本,如果调用失败,是没有相应的机制去处理的,那么接下来就实现调用失败的处理机制,这里简称方法回调

代码如下:

@FeignClient(value = "xxx", path = "xxx", fallbackFactory = DictDefineFallbackFactory.class)
public interface DictDefineFeign {

}

解释:使用@FeignClient注解的fallbackFactory属性,其值是一个类,代码如下:

@Component
@Slf4j
public class DictDefineFallbackFactory implements FallbackFactory<DictDefineFeign> {

  @Override
  public DictDefineFeign create(Throwable throwable) {
    log.error("字典定义服务调用失败:" + throwable.getMessage());
    return new DictDefineFeign() {
   
      @Override
      public ResponsePacket<ResponseTreeEntityDto> tree(String[] codes) {
        return ResponsePacket.generateFail("字典定义服务--->获取树状字典失败");
      }

    };
  }
}

四. 踩坑记录

测试暴露接口的过程中,遇到了一系列形如“xxxClass can not find、xxxMethod is not define”,一般都是对应的依赖没有下载下来。尤其是依赖中的依赖,有些是设置了<optional>true</optional>,这个作用是只允许此依赖所在的项目使用,即使别的项目引用了这个依赖,都无法下载下来。

因此解决方法是调整依赖,或者单独引入缺失的依赖

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐