导读:通过前面两篇文章我们准备好了微服务的基础环境并让accout-service 和 product-service对外提供了增删改查的能力,本篇我们的内容是让order-service作为消费者远程调用accout-service和product-service的服务接口。
在开始今天的正餐之前我们先把上篇文章中那个丑陋的接口返回给优化掉,让所有的接口都有统一的返回结构。
<dependency>
<groupId>com.jianzh5.cloud</groupId>
<artifactId>cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>复制代码
@Data
public class ResultData<T> {
/** 结果状态 ,正常响应200,其他状态码都为失败*/
private int status;
private String message;
private T data;
private boolean success;
private long timestamp ;
... 提供一些静态方法 ...
}复制代码
accout-service 和 product-service 模块中 controller 层的返回结构,改造完的代码如下: @RestController
@Log4j2
public class AccountController {
@Autowired
private AccountService accountService;
@PostMapping("/account/insert")
public ResultData<String> insert(@RequestBody AccountDTO accountDTO){
log.info("insert account:{}",accountDTO);
accountService.insertAccount(accountDTO);
return ResultData.success("insert account succeed");
}
@PostMapping("/account/delete")
public ResultData<String> delete(@RequestParam String accountCode){
log.info("delete account,accountCode is {}",accountCode);
accountService.deleteAccount(accountCode);
return ResultData.success("delete account succeed");
}
@PostMapping("/account/update")
public ResultData<String> update(@RequestBody AccountDTO accountDTO){
log.info("update account:{}",accountDTO);
accountService.updateAccount(accountDTO);
return ResultData.success("update account succeed");
}
@GetMapping("/account/getByCode/{accountCode}")
public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode){
log.info("get account detail,accountCode is :{}",accountCode);
AccountDTO accountDTO = accountService.selectByCode(accountCode);
return ResultData.success(accountDTO);
}
}复制代码
在 SpringCloud 体系中,所有微服务间的通信都是通过 Feign 进行调用, Feign 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像使用 HttpClient 、 OKHttp3 等组件通过封装HTTP请求报文的方式调用。 Feign 通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。而且 Feign 默认集成了负载均衡器 Ribbon ,不需要自己实现负载均衡逻辑。
Feign 是 SpringCloud 的组件,在引入 Feign 之前我们先看看 Spring Boot , Spring Cloud , Spring Cloud Alibaba 三者之间的关系,防止在业务中引入了错误的版本。
引入SpringCloud版本依赖
在项目主pom
中引入SpringCloud依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>复制代码
Feign 的模块中引入 openfeign 依赖 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>复制代码
Feign 接口,接口的定义和返回值需要跟 Controller 层保持一致 @FeignClient(name = "account-service")
public interface AccountFeign {
@PostMapping("/account/insert")
ResultData<String> insert(@RequestBody AccountDTO accountDTO);
@PostMapping("/account/delete")
ResultData<String> delete(@RequestParam("accountCode") String accountCode);
@PostMapping("/account/update")
ResultData<String> update(@RequestBody AccountDTO accountDTO);
@GetMapping("/account/getByCode/{accountCode}")
ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode);
}复制代码
在接口上添加注解 @FeignClient(name = "account-service") ,表明这是一个Feign客户端,name属性的配置表示我这个接口最终会转发到 accout-service 上。
正如回字有多种写法,这里 Feign 也有多种使用方式。第一种就是我们这里介绍的, Feign和生产者的RequestMapping保持一致 ,大家可以看看上面改造后的 AccountController 和 AccountFeign 一模一样有米有。
第二种方式就是让我们的 Controller 直接实现Feign接口,不再需要写RequestMapping ,如:
@RestController
@Log4j2
public class ProductController implements ProductFeign {
@Autowired
private ProductService productService;
@Override
public ResultData<String> insert(@RequestBody ProductDTO productDTO){
log.info("insert product:{}",productDTO);
productService.insertProduct(productDTO);
return ResultData.success("insert product succeed");
}
}复制代码
Feign 接口层的依赖 <dependency>
<groupId>com.jianzh5.cloud</groupId>
<artifactId>account-feign</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>复制代码
product-service 启动类上添加 @EnableFeignClients 注解 @SpringBootApplication
@RestController
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.javadaily.feign.*")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}复制代码
@RestController
public class OrderController {
@Autowired
private AccountFeign accountFeign;
@Autowired
private ProductFeign productFeign;
@PostMapping("/order/getAccount/{accountCode}")
public ResultData<AccountDTO> getAccount(@PathVariable String accountCode){
return accountFeign.getByCode(accountCode);
}
@PostMapping("/order/insertAccount")
public ResultData<String> insertAccount(AccountDTO accountDTO){
return accountFeign.insert(accountDTO);
}
@PostMapping("/order/updateAccount")
public ResultData<String> updateAccount(AccountDTO accountDTO){
return accountFeign.update(accountDTO);
}
@PostMapping("/order/deleteAccount/{accountCode}")
public ResultData<String> deleteAccount(@PathVariable String accountCode){
return accountFeign.delete(accountCode);
}
}复制代码
OrderController 中的接口,验证下接口请求结果:
联调成功,搞定收工! 使用feign过程中有以下几点需要注意,否则一不小心你就会掉进坑里。(我不会告诉你我当时在坑里踩了多久才爬上来)
Feign不支持直接使用对象作为参数请求
接口中如果有多参数需要用实体接收,要么把参数一个一个摆开,要么在对象参数上加上 @RequestBody 注解,让其以json方式接收,如:
@PostMapping("/account/insert")ResultData<String> insert(@RequestBody AccountDTO accountDTO);复制代码
@EnableFeignClients(basePackages = "com.javadaily.feign.*")
所以这里推荐你们在开发中所有feign模块最好能统一包名前缀 com.javadaily.feign delete(@RequestParam("accountCode") String
accountCode);`
否则你会看到类似如下的错误:
Caused by: java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0 这个异常
getByCode(@PathVariable(value =
"accountCode") String accountCode);`
否则你也会看到类似如下的错误 @PathVariable(value = "accountCode") String accountCode
Feign 超时时间配置 order-service 配置文件中增加feign超时时间配置 feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000复制代码
否则你会经常看到如下所示的错误:
java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method) ~[?:1.8.0_112]复制代码
至此我们已经完成了项目公共返回接口的统一并且成功使用Feign调用远程生产者的服务,那么本期的“SpringCloud Alibaba微服务实战三 - 服务调用”篇也就该结束啦,咱们下期有缘再见!
欢迎扫码关注微信公众号或 个人博客