SpringCloud中Feign的基本使用

在使用SpringCloud生态的时候,微服务之间会进行调用,一般我们有两个选择。一是选择使用RestTemplate,二是使用Feign。二者都是基于HTTP的调用,在RestTemplate上加上@LoadBalanced注解,即可生成Ribbon的代理对象,直接实现负载均衡。Feign直接使用声明式调用,更加符合大家平时写接口的习惯,下面主要针对Feign做下介绍。

1、依赖引入

<dependency><!-- consul 服务发现 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency><!-- consul 服务调用 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency><!-- 健康检查 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependencyManagement>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.1.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
</dependencyManagement>

2、服务端

import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Desription: 公共配置服务启动类
 * @Author: yangchenhui
 */
@SpringBootApplication
@EnableDiscoveryClient
public class CommonCfgApplication {

    private static final Logger logger = LoggerFactory.getLogger(CommonCfgApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(CommonCfgApplication.class, args);
        logger.info("****************  common-cfg Server Started  ****************");
    }

}

服务提供方工程结构:

API接口:

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @Desription: 参数获取
 * @Author: yangchenhui
 */
@FeignClient(name = CommonCfgInfoConstant.COMMON_CFG_SERVICE_NAME, path = CommonCfgInfoConstant.COMMON_CFG_CONTEXT_PATH)
public interface ParamService {

    @RequestMapping(value = "/getParamCode", method = RequestMethod.GET)
    String getParamCode(@RequestParam("paramCode") String paramCode);
    
}

3、消费者方的使用方式

3.1、由服务方提供API包,消费端集成使用

这种方式消费者端使用比较方便,入参出参,接口定义都不用写,可以直接注入使用。缺点是需要引入jar包,可能jar中提供的接口也不是这个工程所需要的,如果是老工程的改造的话,还有可能出现同名的Class,这样导包的时候也不太方便。

3.2、由消费方自己写接口,初入参

这种方式消费者方可以选择性的使用服务提供方的接口,并且不需要引入依赖。缺点就是同样的一个接口,需要在多个工程中都写同样的代码,造成代码的重复。如果服务方接口升级,却没有通知到调用方,就有可能造成接口的不兼容。使用上一种方法,就可以通过maven来进行版本管理,可控性稍微强一点。

个人还是比较推荐使用由服务方提供API包的方式,具体使用哪种方式,大家还是跟小组成员商量共同决定。在引入其他服务的的接口时,最好加入防腐层,这样服务提供放的接口变了,我们也只需要修改防腐层即可。

消费方启动类:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
// feign的接口在哪个包下,就扫描哪个包
@EnableFeignClients(basePackages = {"x.x.x.x", "x.x.x.x.x"})
@EnableDiscoveryClient
public class ConsumerServer {

    private static final Logger logger = LoggerFactory.getLogger(ConsumerServer.class);

    public static void main(String[] args) {
        SpringApplication.run(ConsumerServer.class, args);
        logger.info("****************  consumer-server Started ****************");
    }

}

再引入API的maven坐标,只要消费者和提供者是在同一个注册中心,就可以无感知的调用服务方的接口了。其实这种方式跟dubbo很像,只不过一个是rpc长连接调用,一个是基于HTTP的调用。

值得注意的是,@EnableFeignClients(basePackages = {“x.x.x.x”, “x.x.x.x.x”})这个一定要把API所在的包扫描进去,不然在引用注入的时候,feign还没有帮接口生成代理对象,使用@Autowired或者@Resource注入的时候会报错。

调用示例:

/**
 * @Desription:
 * @Author: yangchenhui
 */
@RestController
public class ConsTestFeginController {

    @Resource
    private ParamService paramService;

    @PostMapping("/consTestFeign7")
    Object testFeign7(@RequestBody Map<String, Object> requestParam, HttpServletRequest request) {
        String paramCode = paramService.getParamCode("CREDIT_MED_STATUS");
        return paramCode;
    }
}

4、如何通过feign传递header参数

4.1、在接口中使用@RequestHeader注解传递

/**
 * 测试有参数的feign调用
 *
 * @param requestParam
 * @param prdCode
 * @return
 */
@RequestMapping(value = "/cfg-server/testFeign3", method = RequestMethod.POST)
Object testFeign3(@RequestBody Map<String, Object> requestParam,
                  @RequestHeader("prdCode") String prdCode);

4.2、使用拦截器

客户端拦截器:

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Desription: Feign请求拦截器(设置请求头)
 * @Author: yangchenhui
 */
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = getHttpServletRequest();
        if (null == request){
            return;
        }
        Map<String,String> headers = getHeaders(getHttpServletRequest());
        for(String headerName : headers.keySet()){
            requestTemplate.header(headerName, getHeaders(getHttpServletRequest()).get(headerName));
        }
    }

    private HttpServletRequest getHttpServletRequest() {
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        } catch (Exception e) {
            return null;
        }
    }

    private Map<String, String> getHeaders(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String key = enumeration.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }
}


import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Desription: Feign配置注册(全局)
 * @Author: yangchenhui
 */
@Configuration
public class FeignSupportConfig {

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new FeignBasicAuthRequestInterceptor();
    }

}

服务端获取header参数:

/**
 * @Desription:
 * @Author: yangchenhui
 */
@RestController
public class ParamController {

    @Resource
    private ParamService paramService;

    @GetMapping(value = "/getParamCode")
    public String getParamCode(String paramCode, HttpServletRequest request) {
        System.out.println(request.getHeader("prdCode"));
        System.out.println(request.getHeader("version"));
        System.out.println(request.getHeader("terminalType"));
        return paramService.getParamCodeValue(paramCode);
    }
}

5、超时时间配置

我们可以配置全局的、也可以针对指定服务配置,还可以将某些超时时间需要进行不同控制的抽取到一个接口当中,通过contextId进行配置管理。

单独抽离接口:

/**
 * @Desription: 将单独控制超时时间的接口独立出来,通过contextId在配置文件中指定配置参数
 * @Author: yangchenhui
 */
@Component
@FeignClient(name = "cfg-server", fallback = CfgServiceImpl.class, contextId = "create-order")
public interface CreateOrderService {

    /**
     * 测试有参数的feign调用
     *
     * @param requestParam
     * @return
     */
    @PostMapping(value = "/cfg-server/testFeign5")
    Object testFeign5(@RequestBody Map<String, Object> requestParam);

}

yml文件配置:

feign:
  hystrix:
    enabled: true
  httpclient:
    connection-timeout: 2500
  client:
    config:
      #default代表所有服务
      default:
        #feign客户端建立连接超时时间
        connect-timeout: 2000
        #feign客户端建立连接后读取资源超时时间
        read-timeout: 2000
      #cfg-server表示当调用cfg-server这个服务时,用下面的配置
      cfg-server:
        connectTimeout: 2000
        readTimeout: 2000
        loggerLevel: full
      x-x-x-server:
        connectTimeout: 2000
        readTimeout: 2000
        loggerLevel: full
#        errorDecoder: com.example.SimpleErrorDecoder
#        retryer: com.example.SimpleRetryer
#        requestInterceptors:
#          - com.example.FooRequestInterceptor
#          - com.example.BarRequestInterceptor
#        decode404: false
#        encoder: com.example.SimpleEncoder
#        decoder: com.example.SimpleDecoder
#        contract: com.example.SimpleContract
      # contextId = create-order的接口,单独配置超时时间
      create-order:
        #feign客户端建立连接超时时间
        connect-timeout: 1000
        #feign客户端建立连接后读取资源超时时间
        read-timeout: 1000

hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE

猜你喜欢

转载自blog.csdn.net/m0_67393157/article/details/124442334