Spring Boot集成OpenFegin

标签: 无 分类: 未分类 创建时间:2022-07-16 02:49:02 更新时间:2024-11-15 10:49:43

前言

在我以前的使用微服务的时候,都是使用 LoadBalancerClient 调用微服务的接口,后来学习若依管理框架的时候,发现了Fegin这个配置,其实就是把远程服务接口,写成本地调用的形式,感觉还是要好好的学习一下。

1.加载Jar包
2.新建一个Feign接口类( ClearBatchFeign.java),并且注明接口实现类
3.创建一个Feign接口实现类(ClearBatchFeignFallback.java),实现Feign接口,需要加上注解@Component自动注入
4.定义DTO类来接受数据(接收的数据要和调用的接口一致)
5.调用接口,实现微服务调用

1.添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.修改启动类@EnableFeignClients

启动类添加@EnableFeignClients,扫描包下所由@FeignClient注解描述的接口。

3.编写@FeignClient接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @FeignClient 注解用于描述远程服务调用接口,这个接口不需要你写实现类,你
* 只需要定义访问规则即可(例如请求方式,请求url,请求参数).
* @FeignClient注解描述的接口的实现类对象会默认交给spring管理,这个bean对象
* 的名字默认就是name属性指定的值,这个name还有一个层面的含义,就是你远程调用的
* 服务名.
* 说明:假如@FeignClient注解中添加了contextId属性,则这个属性值默认
* 会作为当前bean对象的名字,此时name的值仅仅作为要调用的服务名对待,一般
* 推荐contextId的值默认为@FeignClient注解描述的接口的名字(首字母小写)
* contextId,作为远程调用服务的唯一标识
*/
//描述远程调用接口,这个接口不需要写实现类,只需要定义规则即可(请求方式,请求url,请求参数)
@FeignClient(name="provider",
contextId = "remoteProviderService",
fallbackFactory = ProviderFallbackFactory.class)
public interface RemoteProviderService {
//@GetMapping表示以get请求方式调用远端服务
//"/provider/echo/{msg}"为远程调用服务的url
@GetMapping("/provider/echo/{msg}")
String echoMsg(@PathVariable("msg") String msg);
}
参考文章:
1.@FeignClient注解 中属性 contextId使用 如果我们使用Feign定义了两个接口,但是目标服务是同一个,那么在SpringBoot启动时就会遇到一个问题,解决方法:1.修改yml配置:spring.main.allow-bean-definition-overriding=true。
2.SpringCloud + Nacos 简单注册消费例子 | Feign调用 | 命名空间namespace 如果@FeignClient中值与spring.service.name对应;不使用url时,Mapping路径为提供者的接口全路径。
3.day03 —远程服务调用Feign 假如@FeignClient注解中添加contextId属性,则这个属性值默认会作为当前bean对象的名字,此时name的值仅仅作为要调用的服务的名对待,一般推荐contextI的值默认为@FeignClient注解描述的接口的名字(首字母小写)

4.编写FallbackFactory

主要实现在服务调用失败的时候,执行的相关操作,也就是上面的ProviderFallbackFactory.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {

@Override
public RemoteProviderService create(Throwable throwable){
log.error("用户服务调用失败:{}", throwable.getMessage());
return new RemoteProviderService() {
@Override
public AjaxResult echoMsg(@@PathVariable("msg") String msg){
return "调用失败";
}
};
}
}

问题

(1) @FeignClient注入service失败
这个主要就是我使用了若依开发环境自定义的 @EnableRyFeignClients 注解,这里的包中没有添加我的包路径,添加上就可以了。

1
@EnableRyFeignClients(basePackages = {"com.ruoyi","com.xxx"})

(2) 服务调用不报错但是不成功
这个问题让我很头大,我在本地测试的时候,同一个微服务的一个接口能访问到,另外的一个接口却访问不了,sendWxMessageBySystem 可以找到,在使用idea进行调试的时候,可以进入到微服务的 sendWxMessageBySystem 方法中。但是同一个微服务的 sendWxMessageByAlarm,却死活找不到微服务,但是也不报错,就是直接返回一个 null,代码跳到下面一个代码中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FeignClient(name = "ph-system",contextId = "remoteWxService",fallbackFactory = RemoteWxFallbackFactory.class)
public interface RemoteWxService {

@PostMapping("sendWxMessageBySystem")
AjaxResult sendWxMessageBySystem(@RequestParam(value = "templateid")String templateid,
@RequestParam(value = "templateparams")String templateparams,
@RequestParam(value = "dbalias")String dbalias,
@RequestParam(value = "sitenos")String sitenos);

@PostMapping("sendWxMessageByAlarm")
AjaxResult sendWxMessageByAlarm(@RequestParam(value = "dbalias")String dbalias,
@RequestParam(value = "siteno_list")String siteno_list);

}

在使用的时候,是这样使用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void sendBreakAlarm(String sitenos){
try {
String dbalias="";
if(this.config!=null){
dbalias=this.config.getDbalias();
}
// 这里可以找到微服务
remoteWxService.sendWxMessageBySystem("","","","");
// 这里就找不到微服务,直接返回了一个 null
AjaxResult sendResult=remoteWxService..sendWxMessageByAlarm("","");
// 判断返回结果
if(sendResult!=null){
logger.error(sitenos+" 设备通讯中断消息发送成功");
}else {
logger.error(sitenos+" 设备通讯中断消息发送失败;消息内容:"+sendResult);
}
}catch (Exception e){
logger.error("sendBreakAlarmMessage",e);
}
}

下面是远程微服务的实现方法,其实非常的简单,就是接收参数,然后进行处理,理论上是没有错误的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping(value = "sendWxMessageByAlarm")
public AjaxResult sendWxMessageByAlarm(@RequestParam(value = "d_t")String d_t,@RequestParam(value = "s_list")String s_list){
try {
if(s_list!=null){
String[] sitenoArray=s_list.split(",");
int sitenocount=sitenoArray.length;
List<WxUser> wxUsers=wxUserService.getUserListByDatabase(s_list); // 所有数据库用户
Map<String,List<String>> userTempMap=new HashMap<>(); // 用户模版
String templateid="rpeVC_inwvGo_mw_PWVf9-LLF64AVw3SO585lPjFGmg"; // 模版id
String dbalias=d_t;
}
}catch (Exception e){
logger.error("sendWxMessageByAlarm",e);
}
return AjaxResult.error();
}

【尝试】
1.我尝试了很多遍重写,在同一个方法里面,一个可以调用,一个不可以调用,我也尝试了使用不同的方法的名字,但是没有效果。
2.尝试修改方法参数名称,也不行。
3.尝试重启idea,删除缓存,也不行。
4.尝试调换方法的位置,也不行。
5.重启电脑也不行,只能新创建一个接口了,但是别人也是在一个接口里面写了好几个方法啊,为什么我的就不行呢?
6.尝试修改 sendWxMessageByAlarm 方法的返回值类型,也不行。
7.将 sendWxMessageByAlarm 请求参数删除。
8.尝试添加 @PostMapping(value = “sendWxMessageByAlarm”,produces =MediaType.APPLICATION_JSON_VALUE) ,也失败了。
9.尝试设置feign的超时时间,也不可以,还是直接返回了null,什么也不报错,但是我的远程接口 sendWxMessageByAlarm 返回的不是 null 啊。

1
2
3
4
5
6
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 3000

我发现了在idea中的一个奇怪的现象,就是我在调试的时候,明明调用的是下面的那个函数,最后出现调试信息的确实上面的那个函数,例如这里的 sitenos 就成了上面的东西。真是玄学啊,玄学啊,我用了很长的时间,还是找不到问题到底出在那里,真是见鬼了。

【解决】
经过长时间的尝试,我竟然发现是因为我在微服务中中配置了拦截器,在拦截器中验证了用户身份

1
2
3
4
5
6
7
8
9
10
11
12
public class UserInterceptor implements HandlerInterceptor {
private static final Logger logger= LoggerFactory.getLogger(UserInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
LoginUser loginUser=tokenService.getLoginUser(request); // 获取用户信息
// 这里直接返回了false
if(loginUser==null){
return false;
}
return true;
}
}

之所以 sendWxMessageBySystem 可以通过,是因为我添加了例外,这样就可以不通过拦截器了,所以可以直接进入到流程的下一步,也就是可以进行调试了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

// 用户身份拦截器
@Bean
public UserInterceptor userInterceptor(){
return new UserInterceptor();
}

// 配置拦截器,主要就是为了验证用户身份信息
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/getUserInfo")
.excludePathPatterns("/sendWxMessageBySystem")
.excludePathPatterns("/error");

}

}

血的教训,血的教训,血的教训,我足足花了半天的时间检查,为什么微服务调用不成功呢?

参考文章:
1.Feign调用微服务,没报错 但访问不到对应微服务
2.SpringCloud FeignClient调用返回结果为null。 发现可能是Feign在组装Http请求去调用远端服务时 请求头参数有问题。
3.feign client设置超时时间 这里是设置feign的超时时间的
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。