OpenFeign使用练习

1、什么是OpenFeign?

      1)feign 是Spring Cloud 组件中一个轻量的RestFul的HTTP服务客户端;Feign内置了 Ribbon,用来做客户端负载均衡,远程的调用服务服务中的服务。

      2)OpenFeign 是spring cloud 对feign进行了封装,使其支持SpringMVC注解;OpenFeign

的注解 @FeignClient 可以解析@RequestMapping 注解下的接口,并通过动态代理的方式生成实现类,在实现类中做负载均衡并远程调用其他服务

2、OpenFeign 使用步骤:

2.1、引入依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2.2、在启动类上添加注解 @EnableFeignClients 来开启OpenFeign远程调用功能

@EnableFeignClients  //开启远程调用
@EnableDiscoveryClient  //开启服务注册,向服务注册中心Nacos注册当前服务
@SpringBootApplication
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }

}

2.3、定义feign远程调用接口

        因为 openfeign 是声明式的,所以需要编写一个接口告诉springcloud 需要调用的目

        标服务。
       远程调用接口类上需要设置注解 @FeignClient,并指定远程调用的目标服务
       定义用于远程调用具体功能的方法,@RequestMapping 注解设置目标服务的具体功

       能的请求地址;接口方法参数是传入目标功能方法的参数,接口返回值用于接收目标

       功能方法的返回值; Feign接口方法签名一般定义成 与 目标功能方法 一致

       代码如下:

       

/**
 * Feign远程调用服务,远程调用coupon
 */
@FeignClient(value = "gulimall-coupon",configuration = {FeignConfig.class})
public interface CouponFeignService {

    @PostMapping("coupon/spubounds/save")
    public R saveSpuBounds(@RequestBody SpuBoundsTo spuBounds);

    @PostMapping("coupon/skufullreduction/saveinfo")
    public R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

2.3)在业务层引入 接口 CouponFeignService ,直接调用CouponFeignService  中的方法

        就可以完成远程调用,如下图所示:

public class Test{

   @Autowired
    private CouponFeignService couponFeignService;

    
    public void FeignTest(){
       SpuBoundsTo spuBounds = new SpuBoundsTo();
       R result = couponFeignService.saveSpuBounds(spuBounds);
            if(result.getCode() != 0){
                log.error("远程调用失败");
            }
    }
}

2.4、feign 网络超时、重试次数和日志配置

        服务之间网络调用肯定会有调用失败、超时的情况,feign默认是不打印日志的,

        针对这些情况我们需要自己设置配置,下面定义一个配置类来配置打印日志、

        失败重试次数、超时时间等

       代码如下:

@Configuration
public class FeignConfig {

    /**
     * 配置请求重试次数
     * @return
     */
    @Bean
    public Retryer feignRetryer(){

        /**
         * period:间隔时间
         * maxPeriod:最大间隔时间
         * maxAttempts:重试次数
         */
        return new Retryer.Default(200, SECONDS.toMillis(2), 10);
    }

    /**
     * 设置请求超时时间
     *默认
     * public Options() {
     * this(10 * 1000, 60 * 1000);
     * }
     *
     * connectTimeout:连接超时,防止由于服务器处理时间长而阻塞调用者。
     * readTimeout:读超时,从连接建立时开始应用,在返回响应时间过长时触发。
     *
     */
    @Bean
    Request.Options feignOptions() {
        return new Request.Options(60 * 1000, 60 * 1000);
    }



    /**
     * 打印请求日志
     * feign日志4种级别:
     *    NONE: 默认不打印任何日志
     *    BASIC: 基础日志,仅打印请求url、请求方法、请求耗时、请求状态码
     *    HEADERS: 除了打印BASIC规定的日志外,还打印请求头和响应头
     *    FULL: 打印请求/应答所有信息,包括请求url、请求方法、耗时、请求状态码、请求消息(包括请求头和请求消息体)
     *          应答消息(响应消息体和消息头)
     *
     * feign日志生效的前提是在工程配置文件application.yml中开启日志打印,如下所示:
     * #开启日志打印
     * logging:
     *   level:
     *     com.gulimall.product: debug  #配置日志级别,表示 com.gulimall.product 包下的代码运行时都打印debug日志
     *
     * @return
     */
    @Bean
    public feign.Logger.Level multipartLoggerLevel() {
        return feign.Logger.Level.FULL;
    }

}

3、@FeignClient注解

     @FeignClient 注解常用属性属性如下:

            (1)name 、serviceId、value:

                     目标服务的名称,用于服务发现;

                    注意:若配置了url属性,则只表示一个名称,并不能用于服务发现

           (2)url:

                   手动指定远程调用服务地址;若配置了url,则请求会不通过Ribbon直接请求

                   这个服务;一般用于测试阶段

           (3)contextId: 

                    表示当前接口生成的Bean在spring中的唯一标识

                    当目标服务(如:gulimall-coupon)的多个接口不想放到同一个FeignClient
                    接口中,但每个FeignClient接口设置的目标服务都是gulimall-coupon,如下;

//接口1
@FeignClient(value="gulimall-coupon")
public interface CouponFeignService {

    @PostMapping("coupon/spubounds/save")
    public R saveSpuBounds(@RequestBody SpuBoundsTo spuBounds);

}


//接口2
@FeignClient(value = "gulimall-coupon")
public interface CouponFeignService2 {

    @PostMapping("coupon/skufullreduction/saveinfo")
    public R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

        

           这时服务启动会报FeignClient接口生成的Bean注入失败的错误,

The bean 'gulimall-coupon.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.

                解决方式:

                   (3.1)在配置文件中加上下边的配置,表示允许spring 中允许名称一样的Bea

spring.main.allow-bean-definition-overriding=true

                    (3.2)在@FeignClient 注解中配置 contextId,如下所示:

//接口1
@FeignClient(value="gulimall-coupon",contextId = "coupon1")
public interface CouponFeignService {

    @PostMapping("coupon/spubounds/save")
    public R saveSpuBounds(@RequestBody SpuBoundsTo spuBounds);

}



//接口2
@FeignClient(value = "gulimall-coupon",contextId = "coupon2")
public interface CouponFeignService2 {

    @PostMapping("coupon/skufullreduction/saveinfo")
    public R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

                             环境启动后,FeignClient接口在spring 中的表示

                             

               注意:

                     在解析注解@EnableFeignClients 注册FeignClient接口过程中设置conxtextId的值

                     时,若没配置conxtextId 值,他会拿serviceId、name 、value 中的一个作为

                     contextId的值,代码如下:   

//注册FeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(2);
        //拿contextId的值作为别名alias的一部分
        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        //若配置了qualifier,则以qualifier 作为别名
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        //注册bean
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }



//
private String getContextId(Map<String, Object> attributes) {
        String contextId = (String)attributes.get("contextId");
        if (!StringUtils.hasText(contextId)) {
            return this.getName(attributes);
        } else {
            contextId = this.resolve(contextId);
            return getName(contextId);
        }
    }

//
String getName(Map<String, Object> attributes) {
        String name = (String)attributes.get("serviceId");
        if (!StringUtils.hasText(name)) {
            name = (String)attributes.get("name");
        }

        if (!StringUtils.hasText(name)) {
            name = (String)attributes.get("value");
        }

        name = this.resolve(name);
        return getName(name);
    }

          (4)fallback:

                    远程调用执行异常的回调类,该异常回调类必须实现 FeignClient接口,

                    如下所示:

@FeignClient(value = "gulimall-coupon",contextId = "coupon2",fallback = ConponFeignFallBack.class)
public interface CouponFeignService2 {

    @PostMapping("coupon/skufullreduction/saveinfo")
    public R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}



@Component
public class ConponFeignFallBack implements CouponFeignService2 {
    @Override
    public R saveSkuReduction(SkuReductionTo skuReductionTo) {
        return R.error("远程调用失败");
    }
}

           (5)fallbackFactory:

                    工厂类,用于生成fallback类实例,通过这个属性我们可以实现每个接口

                   通用的容错逻辑,但不需要实现feignclient接口,如下:

@FeignClient(value="gulimall-coupon",contextId = "coupon1",fallbackFactory = ConponFeignFallBackFactory.class)
public interface CouponFeignService {

    @PostMapping("coupon/spubounds/save")
    public R saveSpuBounds(@RequestBody SpuBoundsTo spuBounds);
}



//泛型表示我们要处理那个FeignClient 接口的异常
@Component
public class ConponFeignFallBackFactory implements FallbackFactory<CouponFeignService> {

    @Override
    public CouponFeignService create(Throwable throwable) {
        return new CouponFeignService() {
            @Override
            public R saveSpuBounds(SpuBoundsTo spuBounds) {
                return R.error("远程回调异常");
            }
        };
    }
}

          (6)path:

                  定义 远程调用的统一前缀

          (7)configuration:

                   指定Feign的配置文件,可以指定多个,如:configuration = {FeignConfig.class}

四、补充

       1、Feign远程调用时,请求不包含页面Http请求的一些头数据的问题?

            可以通过 feign.RequestInterceptor(拦截器) 的来解决这个问题,通过

            RequestInterceptor 中的 apply 方法将 页面Http请求的请求头数据同步到 Feign 请求中

            注意:若当前环境中存在 feign.RequestInterceptor 的实例,Feign 请求会自动调用

           示例代码如下:

              

@Configuration
public class FeignConfig {

    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor() {
            /**
             *
             * @param requestTemplate Feign 请求
             */
            @Override
            public void apply(RequestTemplate requestTemplate) {
                //1、获取当前HttpRequest 请求的请求头数据
                //获取所有的请求属性
                ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                //获取HttpRequest,HttpRequest
                HttpServletRequest request = requestAttributes.getRequest();
                //根据key获取http请求中的数据,如:Cookie
                String cookie = request.getHeader("Cookie");

                //2、将http请求的请求头数据同步到Feign请求中
                //将cookie 添加到Feign请求中
                requestTemplate.header("Cookie",cookie);
            }
        };
    }
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值