前言
主要介绍了feign client的参数的用户,并列举了开发中的一个实际用例
一、Feign是什么?
- Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
- Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
- Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
- Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
- Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。
二、使用说明
1.引入依赖
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.FeignClient注解的参数说明
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
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
@Inherited
public @interface FeignClient {
@AliasFor("name")
String value() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
/** @deprecated */
@Deprecated
String qualifier() default "";
String[] qualifiers() 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;
}
value和name
如果没有配置url,那么配置的值将作为服务的名称,用于服务的发现。
注意配置的内容是提供接口的服务的名字,对用服务发现、走网关调用,由这个参数控制。
这两个是一样的效果,内容互相引用
contextId
如果同一个工程中出现两个接口使用一样的服务名称会报错。原因是Client名字注册到容器中重复了。这种情况可以使用contextId来解决问题。
比如我们有个user服务,但user服务中有很多个接口,我们不想将所有的调用接口都定义在一个类中,比如UserFeignClientA、UserFeignClentB,当不同的Feign的name一致的时候,这时候Bean的名称就会冲突,解决方式可以通过指定不同的contextId来解决问题。
配置了contextId属性,就会采用contextId作为bean的名称注入进容器中,如果没有配置就会去找value然后是name
在真正的项目开发中,这种情况很少,一般是定义在一个类中,方便管理。
另外在注册FeignClient中,这个属性还会作为Client别名的一部分,如果配置了qualifier,会有限使用qualifier作为别名。
// 拼接别名
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
// 配置了qualifier优先用qualifier
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
url
用于配置指定的地址,相当于使用http的形式直接请求这个服务,不经过注册中心。如果配置了这个属性,name属性(通过注册中心调用目标服务)将会被覆盖,不会通过配置中心调用服务。
configuration
这个属性是配置feign的配置类,在配置类中可以定义Feign的Encoder、Decoder、loglevel、contract、鉴权信息等
fallback
fallback
定义容错的处理类,也就是回退逻辑,
fallback
的类必须实现
Feign Client
的接口,无法知道熔断的异常信息,只是重写了回退方法。
fallbackFactory
也是容错的处理,可以通过输出Throwable(调用异常的原因)知道熔断的异常信息。
用线程抛出了异常,可以看到底层具体的问题。
path
path
定义当前
FeignClient
访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。
qualifier
如果由于某些特殊原因,你必须得去掉
primary=true
的设置,这种情况下我们怎么进行注入,我们可以配置一个
qualifier
,然后使用
@Qualifier
注解进行注入
@FeignClient(name = "optimization-user", path="user", qualifier="userRemoteClient")
public interface UserRemoteClient {
@GetMapping("/get")
public User getUser(@RequestParam("id") int id);
}
feign client的注入
@Autowired
@Qualifier("userRemoteClient")
private UserRemoteClient userRemoteClient;
primary
primary对应的是@Primary注解,默认为true,官方这样设置也是有原因的。当我们的Feign实现了fallback后,也就意味着FeignClient有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,所以我们需要设置一个优先级高的,@Primary注解就是干这件事情的。
实际用例
在微服务的框架中,每个服务一般会再定义一个api的模块,用来专门存放feign client。
假设在job模块调用order的模块的接口
首先在order模块的api中定义feign client
1.导入feign的依赖
2.设置接口类
在这里,我定义了contextId,value,fallbackFactory,path四个参数信息
package com.qingdao2world.settle.api;
import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
import com.qingdao2world.settle.api.factory.RemoteBizExpressMonthDetailFallbackFactory;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.ServiceNameConstants;
import com.ruoyi.common.core.domain.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author linaibo
*/
@FeignClient(contextId = "remoteBizExpressMonthDetailService",value= ServiceNameConstants.SETTLE_SERVICE,fallbackFactory= RemoteBizExpressMonthDetailFallbackFactory.class,path="/express")
public interface RemoteBizExpressMonthDetailService {
@GetMapping("/selectDetails")
public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
@GetMapping("/updateDetail")
public R<String> updateCompanyByLogistics(@RequestParam("logisticsNo") String logisticsNo,@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}
3.设置容错处理类
这里使用的fallbackFactory,因为可以看到出错的原因
package com.qingdao2world.settle.api.factory;
import com.qingdao2world.settle.api.RemoteBizExpressMonthDetailService;
import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
import com.ruoyi.common.core.domain.R;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.util.List;
/**
* @author linaibo
* @version 1.0
* Create by 2022/8/17 15:12
*/
public class RemoteBizExpressMonthDetailFallbackFactory implements FallbackFactory<RemoteBizExpressMonthDetailService> {
@Override
public RemoteBizExpressMonthDetailService create(Throwable cause) {
return new RemoteBizExpressMonthDetailService(){
@Override
public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(Integer expressStatus, String source) {
return R.fail("查询快递费用明细失败" + cause.getMessage());
}
@Override
public R<String> updateCompanyByLogistics(String logisticsNo, Integer expressStatus, String source) {
return R.fail("更新快递费用明细失败" + cause.getMessage());
}
};
}
}
4.将容错处理类交由spring容器进行管理
将容错处理类添加在下面这个文件中
billsys-api/billsys-api-settle/src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qingdao2world.settle.api.factory.RemoteBizExpressMonthDetailFallbackFactory,\
com.qingdao2world.settle.api.factory.RemoteBmsDayBillsDetailServiceFallbackFactory
5.在order模块定义一个apiController专门处理feign调用的接口
在order模块中api文件夹中专门设置了一个controler类来处理feign的调用,可以和其他的接口调用区分开,使整个框架更加清晰。可以让这个controller类实现feign client的接口,这样可以避免一些错误。
package com.qingdao2world.settle.api;
import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
import com.qingdao2world.settle.service.IBmsExpressMonthDetailService;
import com.ruoyi.common.core.domain.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author linaibo
* @version 1.0
* Create by 2022/8/17 13:43
*/
@RestController
@RequestMapping("/express")
public class ApiBmsExpressMonthDetailController {
@Autowired
private IBmsExpressMonthDetailService expressMonthDetailService;
@GetMapping("/selectDetails")
public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(required = false) String source) {
List<BmsExpressMonthDetailDto> bmsExpressMonthDetailDtoList = expressMonthDetailService.selectBmsExpressMonthDetailByStatus(expressStatus);
return R.ok(bmsExpressMonthDetailDtoList);
}
@GetMapping("/updateDetail")
public R<String> updateCompanyByLogistics(@RequestParam("logisticsNo") String logisticsNo, @RequestParam("expressStatus") Integer expressStatus, @RequestHeader(required = false) String source) {
int updateNum = expressMonthDetailService.updateCompanyByLogistics(logisticsNo, expressStatus);
return updateNum > 0 ? R.ok("快递信息锁定成功") : R.fail("快递信息锁定失败");
}
}
6.在job模块中引入order的api模块的依赖
<dependency>
<groupId>com.qingdao2world</groupId>
<artifactId>billsys-api-settle</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
7.在job模块的启动类中天添加@EnableFeignClients注解
8.在job模块的service中注入order的api模块的feign client,直接使用就可以
package com.ruoyi.job.queue.disruptor.expressDetailWmsProcess;
import cn.hutool.json.JSONUtil;
import com.lmax.disruptor.WorkHandler;
import com.qingdao2world.settle.api.RemoteBizExpressMonthDetailService;
import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
import com.qingdao2world.settle.api.domain.ExpressWmsDto;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.ExpressStatusEnum;
import com.ruoyi.job.manager.WmsManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
/**
* 推送wms系统
*
* @author linaibo
*/
@RequiredArgsConstructor
@Slf4j
public class ExpressDetailWmsProcessEventHandler implements WorkHandler<ExpressDetailWmsProcessEvent> {
private final RemoteBizExpressMonthDetailService remoteBizExpressMonthDetailService;
private final WmsManager wmsManager;
@Override
public void onEvent(ExpressDetailWmsProcessEvent event) {
//锁住信息,将状态更新为对接中
BmsExpressMonthDetailDto express = event.getValue();
R<String> result = remoteBizExpressMonthDetailService.updateCompanyByLogistics(express.getLogisticsNo(), ExpressStatusEnum.DOCKING.expressStatus, SecurityConstants.INNER);
if(!result.isSuccess()){
log.error("快递明细更新失败"+ JSONUtil.toJsonStr(express));
event.setStop(true);
return;
}
//将信息推送到wms
ExpressWmsDto expressWmsDto = new ExpressWmsDto();
BeanUtils.copyProperties(express,expressWmsDto);
R<String> result2 = wmsManager.importExpress(expressWmsDto);
if (!result2.isSuccess()) {
event.setStop(true);
log.info("快递信息推送WMS系统失败" + JSONUtil.toJsonStr(expressWmsDto));
//更新快递状态为3(对接失败)
remoteBizExpressMonthDetailService.updateCompanyByLogistics(express.getLogisticsNo(),ExpressStatusEnum.DOCKFAIL.expressStatus, SecurityConstants.INNER);
return;
}
}
}
api模块的构成
注意:
feign client中的请求url一定要和被请求接口在controller中的url保持一致
参数一定要设置正确
容错处理类要明确出错的原因,方便对异常的处理
版权归原作者 linab112 所有, 如有侵权,请联系我们删除。