Spring-Cloud-Feign-03
1、快速入门
1、创建一个提供者
这里就拿用户下订单举例
-
创建一个SpringCloud项目,引入以下依赖即可
-
项目创建好后,记得修改
pom.xml
文件的springcloud
依赖 -
然后在启动类上添加
@EnableEurekaClient
注解开启eureka服务 -
配置
application.xml
文件,注册到eureka上 -
修改完后,创建一个下订单的Controller,编写一个接口用来测试
package com.tcc.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 宇辰 * @date 2022/9/27-8:59 **/ @RestController public class TestController { @RequestMapping("buyCar") public String buyCar(){ return "劳斯莱斯-幻觉"; } }
-
最后,启动项目,打开eureka地址查看
order-a
服务是否注册成功
2、创建一个调用者(主要)
调用者需要引入OpenFeign依赖
-
创建项目和上面的步骤一样,除了创建Controller,依赖引入如下
-
创建好项目,修改好pom文件,配置好xml文件,配置好Eureka后,编写Controller
-
Controller需要调用
order-a
服务提供的接口,步骤如下 -
先在启动类上添加注解
@EnableFeignClients // 开启feign的客户端,才可以帮助我们发起调用
-
添加好后,我们使用OpenFeign写法,创建一个文件夹,在里面创建接口,名字为
UserOrderFeign
package com.tcc.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; /** * @author 宇辰 * @date 2022/10/3-11:32 **/ @FeignClient("order-a") // value 为被调用的服务的应用名称 public interface UserOrderFeign { /** * 需要调用哪个接口,就写它的方法签名 * 方法签名就是包含一个方法的所有属性 * @return */ @RequestMapping("buyCar") public String buyCar(); }
-
编写好接口后,我们在Controller里面自动注入这个接口,然后调用接口里面的方法
package com.tcc.controller; import com.tcc.feign.UserOrderFeign; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author 宇辰 * @date 2022/10/3-11:29 **/ @RestController public class TestController { // 注入写好的Feign接口 @Autowired private UserOrderFeign userOrderFeign; @RequestMapping("doOrder") public String doOrder(){ String doOrder = userOrderFeign.buyCar(); return doOrder; } }
-
编写完毕后我们访问
http://localhost:8090/doOrder
调用接口,查看是否可以成功调用
2、调用超时设置
测试:在
order-a
服务提供的接口里延迟两秒再返回内容,看看user-a
是否可以正常获取
- order-a接口修改:
-
修改完毕后,重启服务,然后再次调用
doOrder
接口,查看结果
结论:
当被调用的服务长时间无响应的时候,就停止访问,默认为1秒
RIbbon默认调用时长为1s,可以修改,超时调整,可以查看DefaultClientConfigImpl
类
ribbon:
ReadTimeout: 5000 # 修改调用时长为5s
ConnecTimeout: 5000 # 修改连接时长为5s
3、OpenFeign调用参数处理
Feign传参确保消费者和提供者的参数列表一致,包括返回值,方法签名要一致
- 通过URL传参,GET请求,参数列表使用
@PathVariable("")
- 如果是GET请求,每个基本参数必须加
@RequestParam("")
- 如果是POST请求,而且是对象集合等参数,必须加
@Requestbody或者@RequestParam
举例:
-
先在
order-a和user-a
两个服务编写User
类package com.tcc.entity; import lombok.*; /** * @author 宇辰 * @date 2022/10/3-19:45 **/ @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Builder public class User { private String name; private Integer age; }
-
然后在
order-a
服务编写好接口package com.tcc.controller; import com.tcc.entity.User; import org.springframework.web.bind.annotation.*; /** * @author 宇辰 * @date 2022/10/3-19:40 **/ @RestController public class ParamController { /** * 路径中传参 使用PathVariable注解 * @param name * @param age * @return */ @GetMapping("testUrl/{name}/and{age}") public String testUrl(@PathVariable("name") String name, @PathVariable("age") Integer age){ return "姓名:"+name+",年龄:"+age; } /** * Get请求传参,使用@RequestParam("name") 后面需要加上value * @param name * @return */ @GetMapping("onParam") public String onParam(@RequestParam("name") String name){ return "姓名:" + name; } /** * Post请求,传入一个对象,使用@RequestBody * @param user * @return */ @PostMapping("onObject") public String oneObject(@RequestBody User user){ return "姓名:"+user.getName()+",年龄:"+user.getAge(); } /** * Post请求,传入一个对象,一个基本参数 对象使用;@RequestBody 基本参数使用:@RequestParam("sex") 后面需要加上value * @param user * @param sex * @return */ @PostMapping("onObjectAndOnParam") public String onObjectAndOnParam(@RequestBody User user, @RequestParam("sex") String sex){ return "姓名:"+user.getName()+",年龄:"+user.getAge()+",性别:" + sex; } }
-
在
user-a
服务的Feign中引入需要调用的方法签名package com.tcc.feign; import com.tcc.entity.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; /** * @author 宇辰 * @date 2022/10/3-11:32 **/ // value 为服务的应用名称 @FeignClient("order-a") public interface UserOrderFeign { /** * 需要调用哪个接口,就写它的方法签名 * 方法签名就是包含一个方法的所有属性 * @return */ @RequestMapping("buyCar") public String buyCar(); @GetMapping("testUrl/{name}/and{age}") public String testUrl(@PathVariable("name") String name, @PathVariable("age") Integer age); @GetMapping("onParam") public String onParam(@RequestParam("name") String name); @PostMapping("onObject") public String oneObject(@RequestBody User user); @PostMapping("onObjectAndOnParam") public String onObjectAndOnParam(@RequestBody User user, @RequestParam("sex") String sex); }
-
在Controller中远程调用服务进行测试
@GetMapping("testParam") public String testParam(){ String s1 = userOrderFeign.testUrl("张三", 20); System.out.println(s1); String s2 = userOrderFeign.onParam("李四"); System.out.println(s2); User user = User.builder() .name("王五") .age(21) .build(); String s3 = userOrderFeign.oneObject(user); System.out.println(s3); user = User.builder() .name("赵六") .age(22) .build(); String s4 = userOrderFeign.onObjectAndOnParam(user, "男"); System.out.println(s4); return "ok"; }
-
访问
http://localhost:8090/testParam
地址,查看是否调用成功 -
结果
传递事件日期参数的坑
传过去的日期参数,会相差12个小时
-
先在
order-a
服务编写接口,参数为Date类型。@GetMapping("oneDate") public Date oneDate(@RequestParam("date") Date date){ return date; }
-
在
UserOrderFeign
接口中写好方法签名@GetMapping("oneDate") public Date oneDate(@RequestParam("date") Date date);
-
然后在
user-a
服务中,编写方法,远程调用服务@GetMapping("oneDate") public Date oneDate(){ Date d = new Date(); System.out.println(d); Date date = userOrderFeign.oneDate(d); System.out.println(date); return date; }
-
访问接口,查看控制台打印,可以发现相差12小时
解决办法
-
可以把日期先转为字符串类型,再传过去
-
可以使用jdk8提供的日期类型
LocalDate now = LocalDate.now(); // 没有问题 LocalDateTime now1 = LocalDateTime.now(); // 会丢失s
-
最好把日期类型放到对象里传入过去
4、手写Feign核心功能
通过打断点,可以发现,我们注入的
UserOrderFeign
是被代理过的
- 接口是不能做事情的
- 如果想做事,必须要有对象
- 那么这个接口肯定是被创建处代理对象的
- 动态代理:1.jdk(java interface 接口 $proxy) 2.cglib(subClass 子类)
- jdk动态代理,只要是代理对象调用的方法,必须走
java.lang.reflect.InvocationHandler#invoke
方法
开始手写
-
我们先在启动类上把
RestTemplet
类给放到Bean中,并添加@LoadBalanced
注解,让Ribbon托管@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
-
在测试类中,使用jdk的动态代理,代理
UserOrderFeign
接口,并重写方法package com.tcc; import com.tcc.feign.UserOrderFeign; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.client.RestTemplate; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @SpringBootTest class UserAApplicationTests { // 自动注入被Ribbon托管的RestTemplet @Autowired private RestTemplate restTemplate; @Test void contextLoads() { // 创建动态代理对象 UserOrderFeign o = (UserOrderFeign) Proxy.newProxyInstance(UserOrderFeign.class.getClassLoader(), new Class[]{UserOrderFeign.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 想要远程调用就必须获得服务名+接口地址,然后拼接:http://服务名/接口地址 最后用RestTemplet调用接口即可 // 通过方法获得类名,通过类名获得类名上的注解,再通过注解获取里面的值(服务名) Class<?> aClass = method.getDeclaringClass(); FeignClient aClassAnnotation = aClass.getAnnotation(FeignClient.class); String orderService = aClassAnnotation.value(); // order-a // 通过方法获得注解,通过注解获得里面的值(接口地址) RequestMapping annotation = method.getAnnotation(RequestMapping.class); String[] paths = annotation.value(); String path = paths[0]; // buyCar // 拼接 http://order-a/buyCay String url = "http://" + orderService + "/" + path; String forObject = restTemplate.getForObject(url, String.class); return forObject; // 劳斯莱斯-幻觉 } }); // 调用方法 走代理对象的invoke方法 String s = o.buyCar(); System.out.println(s); // 劳斯莱斯-幻觉 } }
5、配置日志
-
在启动类上把
Feign
的Level
类注入到Bean中@Bean public Logger.Level level(){ return Logger.Level.FULL; }
-
在配置文件中进行配置
logging: level: com.tcc.feign.UserOrderFeign: debug # 打印这个接口下的日志
-
访问接口进行测试