Skip to content
大纲

Feign 相互调用

概述

Feign 是Spring Cloud Netflix组件中的一量级Restful的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon和RestTemplate, 实现了WebService的面向接口编程,进一步降低了项目的耦合度。

服务调用
顾名思义,就是服务之间的接口互相调用,在微服务架构中很多功能都需要调用多个服务才能完成某一项功能。

为什么要使用Feign

  • Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。
  • Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。

Feign vs OpenFeign

  • Feign 内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
  • Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign 官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。
  • Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用Spring Cloud孵化了OpenFeign。并且支持了Spring MVC的注解,如@RequestMapping,@PathVariable等。OpenFeign的@FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。

如何使用

  1. 添加依赖
xml
<!-- spring cloud openfeign -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 新建FeignGoodsService.java服务接口
java
package cn.bizspring.cloud.goods.common.feign;

import cn.bizspring.cloud.common.core.enums.SecurityEnum;
import cn.bizspring.cloud.common.core.enums.ServiceNameEnum;
import cn.bizspring.cloud.goods.common.entity.Goods;
import cn.bizspring.cloud.goods.common.entity.Sku;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 * @author www.bizspring.cn
 */
@FeignClient(contextId = "feignGoodsService", value = ServiceNameEnum.GOODS_SERVICE)
public interface FeignGoodsService {

	/**
	 * 根据实体携带参数,查找实体对象
	 * @return 实体对象,若不存在则返回null
	 */
	@PostMapping("/provider/goods/find_all_by_entity")
	public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from);

	/**
	 * 查找实体对象
	 * @param id
	 *            ID
	 * @return 实体对象,若不存在则返回null
	 */
	@GetMapping("/provider/goods/find")
	public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);
  1. provider下新建GoodsController,商品逻辑降级实现
java
package cn.bizspring.cloud.goods.admin.controller.provider;

import cn.bizspring.cloud.common.core.controller.BaseController;
import cn.bizspring.cloud.common.core.enums.SecurityEnum;
import cn.bizspring.cloud.common.security.annotation.Inside;
import cn.bizspring.cloud.marketing.common.entity.Marketing;
import cn.bizspring.cloud.goods.admin.service.GoodsService;
import cn.bizspring.cloud.goods.common.entity.Goods;
import cn.bizspring.cloud.goods.common.entity.Sku;
import cn.bizspring.cloud.store.common.entity.Store;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 *Provider Controller - 商品
 */
@Slf4j
@AllArgsConstructor
@Api(value = "goods", tags = "provider - 商品")
@RestController("providerGoodsController")
@RequestMapping("/provider/goods")
public class GoodsController extends BaseController {

	/**
	 * 每页记录数
	 */
	private static final int PAGE_SIZE = 12;

	private GoodsService goodsService;


	/**
	 * 查找实体对象
	 *
	 * @param id
	 *            ID
	 * @return 实体对象,若不存在则返回null
	 */
	@Inside
	@GetMapping("/find")
	@ApiOperation(value = "查找实体对象", notes = "查找实体对象")
	@ApiOperationSupport(order = 1)
	public Goods find(@RequestParam("id") String id, @RequestHeader(SecurityEnum.FROM) String from){
		return goodsService.find(id);
	}

	/**
	 * 根据实体对象携带参数 查询对象
	 * @return
	 */
	@Inside
	@ApiOperation(value = "根据实体对象携带参数 查询对象", notes = "根据实体对象携带参数 查询对象")
	@ApiOperationSupport(order = 1)
	@PostMapping("/find_all_by_entity")
	public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from){
		return  goodsService.findAllByEntity(goods);
	}
  1. 消费者CartServiceImpl.java 在 getGiftNames 方法中 feignGoodsService.find 调用远程服务
java
package cn.bizspring.cloud.order.admin.service.impl;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.*;

/**
 * Service - 购物车
 */
@Service
@AllArgsConstructor
public class CartServiceImpl extends BaseServiceImpl<Cart> implements CartService {

	// feign远程Goods(产品)服务
	private FeignGoodsService feignGoodsService;	

	/**
	 * 获取赠品名称
	 * @param store 店铺
	 * @return 赠品名称
	 */
	public List<String> getGiftNames(Cart cart, Store store) {
		List<String> giftNames = new ArrayList<>();
		for (Sku gift : getGifts(cart, store)) {
			Goods goods = feignGoodsService.find(gift.getGoodsId(), SecurityEnum.FROM_IN);
			if (CommonUtils.isNotNull(goods)) {
				giftNames.add(goods.getName());
			}
		}
		return giftNames;
	}
  1. 启动类添加@EnableBaseFeignClients注解。

负载均衡

Feign默认集成了Ribbon,Nacos也很好的兼容了Feign,默认实现了负载均衡的效果。

请求传参

  • Get方式传参,使用@PathVariable、@RequestParam注解接收请求参数
java
@GetMapping("/provider/goods/find")
	public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);
  • Post方式传参,使用@RequestBody注解接收JSON对象请求参数。
java
@PostMapping("/provider/goods/find_all_by_entity")
	public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from);

Http连接池

  • 两台服务器建立HTTP连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。
  • Feign的HTTP客户端支持3种框架:HttpURLConnection、HttpClient、OkHttp。默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient、okhttp作为底层的通信框架。

如:将Feign的HTTP客户端工具修改为okhttp。

  1. 添加依赖
xml
<!-- feign httpclient -->
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
  </dependency>
  1. 全局配置

nacos 中 application-dev.yml配置文件

yaml
# feign 配置
feign:
  sentinel:
    enabled: true
  okhttp:
    enabled: true

日志配置

浏览器发起的请求可以通过F12查看请求和响应信息。
Feign的负载均衡底层用的就是Ribbon,所以请求超时其实就只需要配置Ribbon参数。

全局配置
nacos 中 application-dev.yml配置文件

# 请求处理的超时时间
ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 10000

@Inside注解

接口访问分为两种,一种是由Gateway网关访问进来,一种是内网通过Feign进行内部服务调用。
如果开放了内部访问的接口,如:

java
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id);

bizspring-order系统模块

java
/**
 * 添加
 */
@ApiOperation(value = "添加", notes = "添加")
@ApiOperationSupport(order = 2)
@PostMapping("/add")
public ResponseEntity add(String skuId, Integer quantity, String currentCartId)	
{
    ....
    Goods goods=feignGoodsService.find(sku.getGoodsId());
	return R.ok(...);
}

这段代码也能通过网关(任意用户token携带)访问到localhost:8088/goods/provider/goods/find,这样暴露出去就很危险了,所以对于这种情况我们可以使用@Inside内部注解。

bizspring-order服务调用
新增Header参数SecurityEnum.FROM

java
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);

bizspring-goods服务降级提供模块

java
@Inside
@GetMapping("/find")
@ApiOperation(value = "查找实体对象", notes = "查找实体对象")
@ApiOperationSupport(order = 1)
public Goods find(@RequestParam("id") String id, @RequestHeader(SecurityEnum.FROM) String from){
    return goodsService.find(id);
}

版权许可