一、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