实现生产者中的接口
项目结构组织:项目由多个Module构成,而每个Module又由api模块、model模块、web模块等构成
需求:现在需要将A项目(生产者,或者叫服务提供者)的web模块中的某些接口暴露出去,要求在api模块中定义。使得B项目(消费者,或者叫客户端)能远程调用A项目暴露的接口
首先实现生产者中的接口,在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[]", allowMultiple = true),
})
//allowMultiple:允许多个,即:数组或集合。
@GetMapping(value = "/tree")
public ResponsePacket<RecordsDTO> loadTree(@RequestParam(value = "codes", required = false) String[] codes) {
//下面为业务逻辑代码
RecordsDTO<List<TreeEntityDTO>> responseDto = new RecordsDTO<>();
Condition condition = new Condition();
List<List<TreeEntityDTO>> treeDict = service.getTreeDict(condition, codes);
responseDto.setRecords(treeDict);
return ResponsePacket.generatePacket(responseDto);
}
}
注意:上面那个类,必须加@RestController注解
暴露接口
首先要引入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 = ServiceNameConstants.WISEFLY_WCENTER, path = ServiceNameConstants.WISEFLY_WCENTER_PATH + "/wcenter/dict/define", fallbackFactory = DictDefineFallbackFactory.class)
public interface DictDefineFeign {
@ApiOperation(value = "获取树状字典")
@ApiImplicitParams({
@ApiImplicitParam(name = "codes", value = "字典编码", paramType = "query", dataType = "String[]"),
})
@GetMapping(value = "/tree")
ResponsePacket<RecordsDTO> loadTree(@RequestParam(value = "codes", required = false) String[] codes);
}
注意:
- 必须加@FeignClient声明
- 其他 value属性的值是yml文件中
spring.application.name
的值,这里建议用yml语法动态获取,如上面代码中所示 (用name属性也行,value属性也行) - 如果yml文件中配置了
server.servlet.context-path
,必须配上path属性。否则后面调用这个接口会出现404问题 - 关于方法的mapping注解,有人说Feign不支持 GetMapping,笔者测试过 是支持的。这应该与某些依赖的版本有关
- 方法参数必须加@RequestParam注解(否则,后面调用该接口会报
too many bad parameter
错误),若是POST请求方式,需要根据情况加@RequestBody注解
远程调用
需要在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 {
}
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);
}
}
这种暴露接口的测试,必须先启动生产者(服务提供者),再启动消费者(客户端)。
我们先启动A项目,再启动B项目,运行上面的测试方法,测试成功。
服务降级处理(fallback)
远程调用失败降级处理,代码如下:
@FeignClient(value = ServiceNameConstants.WISEFLY_WCENTER, path = ServiceNameConstants.WISEFLY_WCENTER_PATH + "/wcenter/dict/define", 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>
,这个作用是只允许此依赖所在的项目使用,即使别的项目引用了这个依赖,都无法下载下来。
因此解决方法是调整依赖,或者单独引入缺失的依赖