0. 前言
关于Feign的调用前面自己在学习微服务的时候没有过多留意,后面到了公司真正调用的时候,一堆问题,连都连不通,要么空指针异常,要么获取不到参数,花了大半个下午还没解决,回家后特地自己建了两个微服务进行实验,觉得要好好总结一下知识点,要不然太对不起我下午花的时间了。
1. Feign 的服务降级
业务失败后,不能直接报错,而应该返回用户一个友好提示或者默认结果,这个就是失败降级逻辑。
实现方式
①方式一:FallbackClass
,无法对远程调用的异常做处理
②方式二:FallbackFactory
,可以对远程调用的异常做处理,我们选择这种
-
Feign 接口
@FeignClient(value = "user-service",fallbackFactory = UserFeignServiceFallBackFactory.class) public interface UserFeignService { @GetMapping("all/{ids}") Result<List<JSONObject>> getAll(@PathVariable String ids); }
-
实现 FallbackFactory<T> 类
public class UserFeignServiceFallBackFactory implements FallbackFactory<UserFeignService> { @Override public UserFeignService create(Throwable throwable) { return new UserFeignService() { @Override public Result<List<JSONObject>> getAll(String ids) { System.out.println(throwable); return null; } }; } }
踩坑点:
不管服务降不降级,在接口上面都不能加 @RequestMapping(),要不然可能会报错
Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource
内部好像是因为在初始化加载bean时,Spring会通过 @Controller 和 @RequesMapping 来判断是否为Controller对象,所以若加了 @RequestMapping 会被Spring误判,导致bean对象创建失败
2. 从Spring获取Feign对象
在启动类配置好 @EnableFeignClients()
后,Spring在启动的时候就会自动生成 Feign接口的实例对象 ,正常情况在Spring管理的对象中进行注入调用一般没什么问题,但是我的一些骚操作,引出了一堆问题:
需求:在项目启动的时候就要调Feign进行一些数据的初始化,所以单独写一个类 InitGetData.class 用来初始化,在启动类中调用
@EnableFeignClients("cn.itcast.order") //开启Feign功能
@MapperScan("cn.itcast.order") //扫描包,配置接口实现类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
InitGetData initGetData = new InitGetData();
initGetData.init();
}
}
在 SpringApplication.run(OrderApplication.class, args) 后面再进行调用,确保Spring服务启动后能注册进 nacos 中来实现Feign连接
我之前 InitGetData.class 写法为:
public class InitGetData {
@Autowired
private UserFeignService userFeignService;
public void init() {
String ids = "柳岩,文二狗,郑爽爽";
Result<List<JSONObject>> result = userFeignService.getAll(ids);
List<JSONObject> jsObjects = result.getData();
System.out.println(jsObjects);
}
}
当时想的是,UserFeignService的实例对象在Spring初始化的时候就生成了,所以能自动注入进去
然而报错了
Exception in thread "main" java.lang.NullPointerException
空指针,因为 InitGetData 实例对象不受Spring管理,而 @Autowired 是在Spring初始化的时候就把所有bean对象生成了并对管理的实例对象进行自动注入,后面不会再监听注入,所以后面调用对象报空指针,因为不受Spring管理,对象没有注入进去
方法1,让Spring来管理该对象,Spring初始化的时候进行注入
@Component
public class InitGetData {
@Autowired
private UserFeignService userFeignService;
public void init() {
String ids = "柳岩,文二狗,郑爽爽";
Result<List<JSONObject>> result = userFeignService.getAll(ids);
List<JSONObject> jsObjects = result.getData();
System.out.println(jsObjects);
}
}
@EnableFeignClients("cn.itcast.order") //开启Feign功能
@MapperScan("cn.itcast.order") //扫描包,配置接口实现类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
// SpringUtils.getBean - 获取Spring容器中的实例对象
InitGetData initGetData = SpringUtils.getBean(InitGetData.class);
initGetData.init();
}
}
拓展:
若想声明为 static 类型来进行自动注入,则写法应为
private static UserFeignService userFeignService;
@Autowired
private void setUserFeignService(UserFeignService userFeignService) {
this.userFeignService = userFeignService;
}
要不然可能会报错,大致原因应该是Spring构造实例对象的时候三级缓存,实例化和属性赋值要分开完成,如果都在实例化这一步进行,会造成bean对象创建失败,具体原因还不是很清楚,因为普通对象之间注入可以正常运行,但是static类型直接注入我报错了
方法2,从Spring容器中拿到实例手动注入
public class InitGetData {
private static UserFeignService userFeignService;
public void init() {
String ids = "柳岩,文二狗,郑爽爽";
Result<List<JSONObject>> result = getUserFeignService().getAll(ids);
List<JSONObject> jsObjects = result.getData();
System.out.println(jsObjects);
}
private static UserFeignService getUserFeignService() {
if (userFeignService == null) {
synchronized (InitGetData.class) {
if (userFeignService == null) {
userFeignService = SpringUtils.getBean(UserFeignService.class);
}
return userFeignService;
}
}
return userFeignService;
}
}
@EnableFeignClients("cn.itcast.order") //开启Feign功能
@MapperScan("cn.itcast.order") //扫描包,配置接口实现类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
InitGetData initGetData = new InitGetData();
initGetData.init();
}
}
拓展:
手动去容器拿实例声明出对象,可以用单例模式-懒汉式(静态内部类)
private static class LazyLoadUserFeignService {
public final static UserFeignService userFeignService = SpringUtils.getBean(UserFeignService.class);
}
public static UserFeignService getUserFeignService() {
return LazyLoadUserFeignService.userFeignService;
}
3. 小结
想复盘下午遇到的问题,结果牺牲了一晚上下班的时间,写了这么久,但还是值得的,至少没白白浪费时间,又把相关知识梳理了一遍,犯错的根本原因还是把原理忘记了,这以后就是低级错误了