spring cloud (六) Feign

一、Feign简述

前面已经介绍过Ribbon了,Ribbon的调用是通过RestTemplate类中的方法进行调用,而Feign是一种声明式rest调用。Feign是Netflix开发的声明式调用,模板化的http客户端,Feign可帮助我们更加快捷,优雅的调用http api,Feign是对RestTemplate的进一步封装。

二、整合Feign

1.添加依赖

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

2.编写启动类,并使用注解@EnableFeignClients

package com.wangcongming.shop.score;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score
 * @Description:
 * @date 2018/5/18 14:46
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@ComponentScan(basePackages = { "com.wangcongming" })
public class ScoreApplication {

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

3.编写feign调用接口

package com.wangcongming.shop.score.feign;

import com.wangcongming.shop.score.entity.ScoreDetail;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score.entity
 * @Description: feign调用
 * @date 2018/5/19 14:24
 */
@FeignClient("user-system")
public interface UserClient {

    @RequestMapping(value="/user/findUser/{id}", method= RequestMethod.GET)
    public String getUserInfo(@PathVariable("id") String id);

    @RequestMapping(value="/user/addScore", method= RequestMethod.GET)
    public String update(@RequestParam Map<String, Object> map);

    @RequestMapping(value="/user/updateScore", method= RequestMethod.POST)
    public String update2(ScoreDetail detail);
}

4.调用

package com.wangcongming.shop.score.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wangcongming.base.ResultMap;
import com.wangcongming.exception.BusinessException;
import com.wangcongming.shop.score.feign.UserClient;
import com.wangcongming.shop.score.rao.RedisRao;
import com.wangcongming.shop.score.service.ScoreService;
import com.wangcongming.util.CollectionUtils;
import com.wangcongming.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score.service.impl
 * @Description:
 * @date 2018/5/19 14:32
 */
@Service
@Slf4j
public class ScoreServiceImpl implements ScoreService {

    @Autowired
    private RedisRao redisRao;
    @Autowired
    private UserClient userClient;

    private final static String USER_SCORE = "user:score:";

    @Override
    public ResultMap addScore(String uid, String score) throws BusinessException {

        if(StringUtils.isAnyEmpty(uid,score)){
            throw new BusinessException("参数不合法");
        }
        //查询用户是否存在
        String userInfo = userClient.getUserInfo(uid);
        if(StringUtils.isEmpty(userInfo)){
            throw new BusinessException("用户不存在");
        }

        JSONObject jsonObject = JSON.parseObject(userInfo);
        String status = jsonObject.getString("status");
        if(!Objects.equals(status,"200")){
            log.info("用户:{}查询失败",uid);
            throw new BusinessException("用户不存在");
        }

        JSONObject data = jsonObject.getJSONObject("data");
        if(CollectionUtils.isEmpty(data)){
            log.info("用户:{}查询不到用户信息",uid);
            throw new BusinessException("用户不存在");
        }

        String key = String.format("%s%s",USER_SCORE,uid);
        Long increment = redisRao.increment(key, Long.parseLong(score));
        log.info("增长之后用户:{}积分为:{},增长积分为:{}",uid,increment,score);
        //--------------------写入明细表--------------------

        //调用接口
        Map<String, Object> map = new HashMap<>();
        map.put("uid",uid);
        map.put("score",score);
        String update = userClient.update(map);
        log.info("积分更新结果:{}",update);
        return ResultMap.ok();
    }
}
package com.wangcongming.shop.score.controller;

import com.wangcongming.aop.Log;
import com.wangcongming.base.ResultMap;
import com.wangcongming.exception.BusinessException;
import com.wangcongming.shop.score.service.ScoreService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score.controller
 * @Description:
 * @date 2018/5/19 14:29
 */
@RestController
@Log
@RequestMapping("/score")
@Slf4j
public class ScoreController {

    @Autowired
    private ScoreService scoreService;

    @GetMapping(value = "/addScore")
    public ResultMap addScore(String uid,String score){
        try {
            return scoreService.addScore(uid,score);
        } catch (BusinessException e) {
            log.error(e.getMessage(),e);
            return ResultMap.error(e.getMessage());
        } catch (Exception e) {
            log.error(e.getMessage(),e);
            return ResultMap.error("添加失败");
        }
    }
}

三、feign使用中的坑

1.feign client中只能使用注解@RequestMapping不能使用@GetMapping和@PostMapping

2.使用注解@PathVariable时必须指明value,不能省略

3.请求参数如果是复杂对象时,不能使用get请求,即使指明是get请求,feign默认也会以post请求去调用

4.get请求多个参数时,必须使用@RequestParam注解指明value,或者使用Map进行参数传递

四、@FeignClient注解

1.FeignClient简述

先看一下源码

package org.springframework.cloud.netflix.feign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    @Deprecated
    String serviceId() default "";

    @AliasFor("value")
    String name() default "";

    String qualifier() default "";

    String url() default "";

    boolean decode404() default false;

    Class<?>[] configuration() default {};

    Class<?> fallback() default void.class;

    Class<?> fallbackFactory() default void.class;

    String path() default "";

    boolean primary() default true;
}

属性代表意思如下

 

  • name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址,最新版本使用url必须指定name属性
  • decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
  • configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
  • fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
  • fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • path: 定义当前FeignClient的统一前缀

serviceId这个属性可以看到已经过期,这里就不说了

2.configuration属性详解

configuration可以针对当前这个client指定配置类,对其他配置类不影响。示例如下:

package com.wangcongming.shop.score.feign;

import com.wangcongming.shop.score.config.FeignConfiguration;
import feign.Param;
import feign.RequestLine;
import org.springframework.cloud.netflix.feign.FeignClient;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score.entity
 * @Description: feign调用
 * @date 2018/5/19 14:24
 */
@FeignClient(name = "user-system",configuration = FeignConfiguration.class)
public interface UserClient2 {

    @RequestLine("GET /user/findUser/{id}")
    public String getUserInfo(@Param("id") String id);
}
package com.wangcongming.shop.score.config;

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

@Configuration
public class FeignConfiguration {

    @Bean
    public Contract feignContract(){
        return new feign.Contract.Default();
    }
}

UserClient2中指定了configuration使用的是FeignConfiguration,在FeignConfiguration中定义了使用Feign的默认契约,此时就不能使用spring的注解了。

注:在使用configuration时,一定不能让@ComponentScan注解扫描到FeignConfiguration类,否则FeignConfiguration中的配置就变成了全局配置。

3.fallback属性

在调用超时或者失败时,有时候需要给一个默认值,那么可以通过fallback属性来指定,具体实现如下:

package com.wangcongming.shop.score.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

/**
 * @author wangcongming
 * @Package com.wangcongming.shop.score.entity
 * @Description: feign调用
 * @date 2018/5/19 14:24
 */
@FeignClient(name = "user-system",fallback = UserClientFallBack.class)
public interface UserClient {

    @RequestMapping(value="/user/findUser/{id}", method= RequestMethod.GET)
    public String getUserInfo(@PathVariable("id") String id);

    @RequestMapping(value="/user/addScore", method= RequestMethod.GET)
    public String update(@RequestParam Map<String, Object> map);
}
package com.wangcongming.shop.score.feign;

import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 当UserClient中的feign调用失败或超时时,会调用该实现类的方法
 */
@Component
public class UserClientFallBack implements UserClient {
    @Override
    public String getUserInfo(String id) {
        return "调用失败";
    }

    @Override
    public String update(Map<String, Object> map) {
        return null;
    }
}

 

  • fallback指定的类一定要使用@Component标注
  • 如果想要使用fallback功能,在旧版本中hystrix对feign的支持是默认打开的,但在新版本中被禁用了,可以使用feign.hystrix.enabled=true配置进行开启(具体是哪个版本开始默认禁用,lz也不知道,知道的可以在评论区告诉lz,非常感谢)。

五、feign日志配置

1.配置application配置文件

logging:
 level:
  com.wangcongming.shop.score.feign.UserClient: DEBUG 

2.添加配置

package com.wangcongming.shop.score.config;

import feign.Logger;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfiguration {

    /**
     * NONE, 不记录日志 (默认)。
     * BASIC, 只记录请求方法和URL以及响应状态代码和执行时间。
     * HEADERS, 记录请求和应答的头的基本信息。
     * FULL, 记录请求和响应的头信息,正文和元数据。
     * @return
     */
    public Logger.Level feignLoggerLevel() {
        //设置日志
        return Logger.Level.FULL;
    }
}

这样就可以看到日志了。

六、Feign请求超时问题

Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了
解决方案有三种,以feign为例。

  • 方法一

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
该配置是让Hystrix的超时时间改为5秒

  • 方法二

hystrix.command.default.execution.timeout.enabled: false
该配置,用于禁用Hystrix的超时时间

  • 方法三

feign.hystrix.enabled: false
该配置,用于索性禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用。

 

推荐一套spring cloud学习视频 下载地址:https://download.csdn.net/download/linhui258/10525913

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值