简介:Feign是声明式(注解)web服务客户端。Feign是一个远程调用的组件(接口、注解)完成http调用,Feign集成了ribbon、ribbon里面集成了eureka。使用feign需要创建一个接口添加feign注解。
依旧沿用之前的项目,创建一个feign的空项目,再创建springboot项目,springboot版本2.3.12.RELEASE,springcloud版本Hoxton.SR12,基本依赖有eureka、spring web、Lombok
-
创建一个order-service项目代码:
@RestController public class OrderController { @GetMapping("doOrder") public String doOrder(){ return "订单接口"; } }server: port: 8080 spring: application: name: order-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka@SpringBootApplication @EnableEurekaClient public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } } -
创建user-service项目代码,这个项目增加一个feign依赖:
server: port: 8081 spring: application: name: user-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka@SpringBootApplication @EnableEurekaClient @EnableFeignClients // 开启feign的客户端功能 public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }@RestController public class UserController { @Autowired public UserOrderFeign userOrderFeign; @GetMapping("userDoOrder") public String userDoOrder(){ System.out.println("用户请求"); //发起一个远程调用 请求order-service String s = userOrderFeign.doOrder(); return s; } }feign接口:
@FeignClient(value = "order-service") public interface UserOrderFeign { @GetMapping("doOrder") String doOrder(); } -
把eureka-server和上面的两个新代码运行起来访问localhost:8081/userDoOrder
-
修改order-service控制层代码
@RestController public class OrderController { @GetMapping("doOrder") public String doOrder(){ try { //模拟操作数据库的耗时 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return "订单接口"; } }重启后,再次访问localhost:8081/userDoOrder 会发现报500 ,控制台报Read timed out超时,这是因为feign封装了ribbon,ribbon默认等待时间1000毫秒,所以超过1s直接报错超时
-
增加user-service配置
# feign只是封装了远程调用的功能,底层还是ribbon,所以修改ribbon的时间 ribbon: ReadTimeout: 3000 # 调用超时时间(毫秒) ConnectTimeout: 3000 # 连接服务的超时时间(毫秒)
重启user-service,访问localhost:8081/userDoOrder,此时正常
-
Feign简单实现
主要是通过动态代理,这里重写一下invoke方法。获取注解的value再拼接url,最后使用restTemplate把拼好的url放进去即可。这里的url其实还是接口名的形式,ribbon会再处理一下把接口变成ip。
@SpringBootTest class UserServiceApplicationTests { @Autowired private RestTemplate restTemplate; @Test void contextLoads() { UserOrderFeign o = (UserOrderFeign) Proxy.newProxyInstance(UserController.class.getClassLoader(), new Class[]{UserOrderFeign.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("重写了invoke"); //拿到对方的ip和端口 并且拿到这个方法上面注解的值 String name = method.getName(); GetMapping annotation = method.getAnnotation(GetMapping.class); String[] paths = annotation.value(); String path = paths[0]; Class> declaringClass = method.getDeclaringClass(); FeignClient annotation1 = declaringClass.getAnnotation(FeignClient.class); String applicationName = annotation1.value(); String url = "http://" + applicationName + "/" + path; String forObject = restTemplate.getForObject(url, String.class); return forObject; } }); String s = o.doOrder(); System.out.println(s); } } -
Feign参数处理
order-service添加一个新的controller
@RestController public class ParamController { @GetMapping("testUrl/{name}/{age}") public String testUrl(@PathVariable("name")String name,@PathVariable("age")Integer age){ System.out.println(name+":"+age); return "ok"; } @GetMapping("oneParam") public String oneParam(@RequestParam(required = false) String name){ System.out.println(name); return "ok"; } @GetMapping("twoParam") public String twoParam(@RequestParam(required = false) String name,@RequestParam(required = false)Integer age){ System.out.println(name); System.out.println(age); return "ok"; } @PostMapping("oneObj") public String oneObj(@RequestBody Order order){ System.out.println(order); return "ok"; } @PostMapping("oneObjOneParam") public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name){ System.out.println(order); System.out.println(name); return "ok"; } }order实体类
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class Order { private Integer id; private String name; private Double price; private Date time; }user-service controller添加一个方法
@GetMapping("testParam") public String testParam(){ String testUrl = userOrderFeign.testUrl("testUrl", 18); System.out.println(testUrl); String oneParam = userOrderFeign.oneParam("oneParam"); System.out.println(oneParam); String twoParam = userOrderFeign.twoParam("twoParam", 18); System.out.println(twoParam); Order order = Order.builder().name("abc").price(100D).time(new Date()).id(1).build(); String oneObj = userOrderFeign.oneObj(order); System.out.println(oneObj); String oneObjOneParam = userOrderFeign.oneObjOneParam(order, "oneObjOneParam"); System.out.println(oneObjOneParam); return "ok"; }实体类也要粘过来
接口添加代码
@GetMapping("testUrl/{name}/{age}") public String testUrl(@PathVariable("name")String name, @PathVariable("age")Integer age); @GetMapping("oneParam") public String oneParam(@RequestParam(required = false) String name); @GetMapping("twoParam") public String twoParam(@RequestParam(required = false) String name,@RequestParam(required = false)Integer age); @PostMapping("oneObj") public String oneObj(@RequestBody Order order); @PostMapping("oneObjOneParam") public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name); -
测试传递时间参数
order-service controller添加一个方法
@GetMapping("testTime") public String testTime(@RequestParam Date date){ System.out.println(date); return "ok"; }user-service controller添加方法
@GetMapping("time") public String testTime(){ Date date = new Date(); System.out.println(date); String s = userOrderFeign.testTime(date); return s; }feign接口添加
@GetMapping("testTime") public String testTime(@RequestParam Date date);访问http://localhost:8081/time结果如下
user-service Mon Jul 11 17:18:57 CST 2022
order-service Tue Jul 12 07:18:57 CST 2022
会发现有14小时差距,所以最好不要单独传递时间参数
可以转成字符串 或者 使用LocalDate 年月日
但是使用LocalDateTime 会丢失秒
-
Feign日志功能
Feign的Logger类有四个日志级别
public enum Level { NONE, BASIC, HEADERS, FULL }- none 无日志
- basic 记录请求方法、url、响应状态码、时间
- headers 记录请求和响应的主要信息
- full 最高的日志级别,记录请求和响应的头、体、元数据
-
如何开启Feign功能
修改配置文件
logging: level: com.example.userservice.feign.UserOrderFeign: debug # 打印该接口下的日志修改启动类
@Bean public Logger.Level level(){ return Logger.Level.FULL; }
aders 记录请求和响应的主要信息
- full 最高的日志级别,记录请求和响应的头、体、元数据
-
如何开启Feign功能
修改配置文件
logging: level: com.example.userservice.feign.UserOrderFeign: debug # 打印该接口下的日志修改启动类
@Bean public Logger.Level level(){ return Logger.Level.FULL; }



