0


Spring Cloud中的Feign调用


前言

主要介绍了feign client的参数的用户,并列举了开发中的一个实际用例


一、Feign是什么?

  1. Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
  2. Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
  3. Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
  4. Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
  5. Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。

二、使用说明

1.引入依赖

  1. <!--服务调用-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>

2.FeignClient注解的参数说明

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by FernFlower decompiler)
  4. //
  5. package org.springframework.cloud.openfeign;
  6. import java.lang.annotation.Documented;
  7. import java.lang.annotation.ElementType;
  8. import java.lang.annotation.Inherited;
  9. import java.lang.annotation.Retention;
  10. import java.lang.annotation.RetentionPolicy;
  11. import java.lang.annotation.Target;
  12. import org.springframework.core.annotation.AliasFor;
  13. @Target({ElementType.TYPE})
  14. @Retention(RetentionPolicy.RUNTIME)
  15. @Documented
  16. @Inherited
  17. public @interface FeignClient {
  18. @AliasFor("name")
  19. String value() default "";
  20. String contextId() default "";
  21. @AliasFor("value")
  22. String name() default "";
  23. /** @deprecated */
  24. @Deprecated
  25. String qualifier() default "";
  26. String[] qualifiers() default {};
  27. String url() default "";
  28. boolean decode404() default false;
  29. Class<?>[] configuration() default {};
  30. Class<?> fallback() default void.class;
  31. Class<?> fallbackFactory() default void.class;
  32. String path() default "";
  33. boolean primary() default true;
  34. }

value和name

如果没有配置url,那么配置的值将作为服务的名称,用于服务的发现。

注意配置的内容是提供接口的服务的名字,对用服务发现、走网关调用,由这个参数控制。

这两个是一样的效果,内容互相引用

contextId

如果同一个工程中出现两个接口使用一样的服务名称会报错。原因是Client名字注册到容器中重复了。这种情况可以使用contextId来解决问题。

比如我们有个user服务,但user服务中有很多个接口,我们不想将所有的调用接口都定义在一个类中,比如UserFeignClientA、UserFeignClentB,当不同的Feign的name一致的时候,这时候Bean的名称就会冲突,解决方式可以通过指定不同的contextId来解决问题。

配置了contextId属性,就会采用contextId作为bean的名称注入进容器中,如果没有配置就会去找value然后是name

在真正的项目开发中,这种情况很少,一般是定义在一个类中,方便管理。

另外在注册FeignClient中,这个属性还会作为Client别名的一部分,如果配置了qualifier,会有限使用qualifier作为别名。

  1. // 拼接别名
  2. String alias = contextId + "FeignClient";
  3. AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  4. boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
  5. // null
  6. beanDefinition.setPrimary(primary);
  7. // 配置了qualifier优先用qualifier
  8. String qualifier = getQualifier(attributes);
  9. if (StringUtils.hasText(qualifier)) {
  10. alias = qualifier;
  11. }
  12. BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
  13. new String[] { alias });
  14. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

url

用于配置指定的地址,相当于使用http的形式直接请求这个服务,不经过注册中心。如果配置了这个属性,name属性(通过注册中心调用目标服务)将会被覆盖,不会通过配置中心调用服务。

configuration

这个属性是配置feign的配置类,在配置类中可以定义Feign的Encoder、Decoder、loglevel、contract、鉴权信息等

fallback

  1. fallback

定义容错的处理类,也就是回退逻辑,

  1. fallback

的类必须实现

  1. Feign Client

的接口,无法知道熔断的异常信息,只是重写了回退方法。

fallbackFactory

也是容错的处理,可以通过输出Throwable(调用异常的原因)知道熔断的异常信息。

用线程抛出了异常,可以看到底层具体的问题。

path

  1. path

定义当前

  1. FeignClient

访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。

qualifier

如果由于某些特殊原因,你必须得去掉

  1. primary=true

的设置,这种情况下我们怎么进行注入,我们可以配置一个

  1. qualifier

,然后使用

  1. @Qualifier

注解进行注入

  1. @FeignClient(name = "optimization-user", path="user", qualifier="userRemoteClient")
  2. public interface UserRemoteClient {
  3. @GetMapping("/get")
  4. public User getUser(@RequestParam("id") int id);
  5. }

feign client的注入

  1. @Autowired
  2. @Qualifier("userRemoteClient")
  3. 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四个参数信息

  1. package com.qingdao2world.settle.api;
  2. import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
  3. import com.qingdao2world.settle.api.factory.RemoteBizExpressMonthDetailFallbackFactory;
  4. import com.ruoyi.common.core.constant.SecurityConstants;
  5. import com.ruoyi.common.core.constant.ServiceNameConstants;
  6. import com.ruoyi.common.core.domain.R;
  7. import org.springframework.cloud.openfeign.FeignClient;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.RequestHeader;
  10. import org.springframework.web.bind.annotation.RequestParam;
  11. import java.util.List;
  12. /**
  13. * @author linaibo
  14. */
  15. @FeignClient(contextId = "remoteBizExpressMonthDetailService",value= ServiceNameConstants.SETTLE_SERVICE,fallbackFactory= RemoteBizExpressMonthDetailFallbackFactory.class,path="/express")
  16. public interface RemoteBizExpressMonthDetailService {
  17. @GetMapping("/selectDetails")
  18. public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
  19. @GetMapping("/updateDetail")
  20. public R<String> updateCompanyByLogistics(@RequestParam("logisticsNo") String logisticsNo,@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
  21. }

3.设置容错处理类

这里使用的fallbackFactory,因为可以看到出错的原因

  1. package com.qingdao2world.settle.api.factory;
  2. import com.qingdao2world.settle.api.RemoteBizExpressMonthDetailService;
  3. import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
  4. import com.ruoyi.common.core.domain.R;
  5. import org.springframework.cloud.openfeign.FallbackFactory;
  6. import java.util.List;
  7. /**
  8. * @author linaibo
  9. * @version 1.0
  10. * Create by 2022/8/17 15:12
  11. */
  12. public class RemoteBizExpressMonthDetailFallbackFactory implements FallbackFactory<RemoteBizExpressMonthDetailService> {
  13. @Override
  14. public RemoteBizExpressMonthDetailService create(Throwable cause) {
  15. return new RemoteBizExpressMonthDetailService(){
  16. @Override
  17. public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(Integer expressStatus, String source) {
  18. return R.fail("查询快递费用明细失败" + cause.getMessage());
  19. }
  20. @Override
  21. public R<String> updateCompanyByLogistics(String logisticsNo, Integer expressStatus, String source) {
  22. return R.fail("更新快递费用明细失败" + cause.getMessage());
  23. }
  24. };
  25. }
  26. }

4.将容错处理类交由spring容器进行管理

将容错处理类添加在下面这个文件中

billsys-api/billsys-api-settle/src/main/resources/META-INF/spring.factories

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. com.qingdao2world.settle.api.factory.RemoteBizExpressMonthDetailFallbackFactory,\
  3. com.qingdao2world.settle.api.factory.RemoteBmsDayBillsDetailServiceFallbackFactory

5.在order模块定义一个apiController专门处理feign调用的接口

在order模块中api文件夹中专门设置了一个controler类来处理feign的调用,可以和其他的接口调用区分开,使整个框架更加清晰。可以让这个controller类实现feign client的接口,这样可以避免一些错误。

  1. package com.qingdao2world.settle.api;
  2. import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
  3. import com.qingdao2world.settle.service.IBmsExpressMonthDetailService;
  4. import com.ruoyi.common.core.domain.R;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.web.bind.annotation.*;
  7. import java.util.List;
  8. /**
  9. * @author linaibo
  10. * @version 1.0
  11. * Create by 2022/8/17 13:43
  12. */
  13. @RestController
  14. @RequestMapping("/express")
  15. public class ApiBmsExpressMonthDetailController {
  16. @Autowired
  17. private IBmsExpressMonthDetailService expressMonthDetailService;
  18. @GetMapping("/selectDetails")
  19. public R<List<BmsExpressMonthDetailDto>> selectBmsExpressMonthDetailByStatus(@RequestParam("expressStatus") Integer expressStatus, @RequestHeader(required = false) String source) {
  20. List<BmsExpressMonthDetailDto> bmsExpressMonthDetailDtoList = expressMonthDetailService.selectBmsExpressMonthDetailByStatus(expressStatus);
  21. return R.ok(bmsExpressMonthDetailDtoList);
  22. }
  23. @GetMapping("/updateDetail")
  24. public R<String> updateCompanyByLogistics(@RequestParam("logisticsNo") String logisticsNo, @RequestParam("expressStatus") Integer expressStatus, @RequestHeader(required = false) String source) {
  25. int updateNum = expressMonthDetailService.updateCompanyByLogistics(logisticsNo, expressStatus);
  26. return updateNum > 0 ? R.ok("快递信息锁定成功") : R.fail("快递信息锁定失败");
  27. }
  28. }

6.在job模块中引入order的api模块的依赖

  1. <dependency>
  2. <groupId>com.qingdao2world</groupId>
  3. <artifactId>billsys-api-settle</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. <scope>compile</scope>
  6. </dependency>

7.在job模块的启动类中天添加@EnableFeignClients注解

8.在job模块的service中注入order的api模块的feign client,直接使用就可以

  1. package com.ruoyi.job.queue.disruptor.expressDetailWmsProcess;
  2. import cn.hutool.json.JSONUtil;
  3. import com.lmax.disruptor.WorkHandler;
  4. import com.qingdao2world.settle.api.RemoteBizExpressMonthDetailService;
  5. import com.qingdao2world.settle.api.domain.BmsExpressMonthDetailDto;
  6. import com.qingdao2world.settle.api.domain.ExpressWmsDto;
  7. import com.ruoyi.common.core.constant.SecurityConstants;
  8. import com.ruoyi.common.core.domain.R;
  9. import com.ruoyi.common.core.enums.ExpressStatusEnum;
  10. import com.ruoyi.job.manager.WmsManager;
  11. import lombok.RequiredArgsConstructor;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.springframework.beans.BeanUtils;
  14. /**
  15. * 推送wms系统
  16. *
  17. * @author linaibo
  18. */
  19. @RequiredArgsConstructor
  20. @Slf4j
  21. public class ExpressDetailWmsProcessEventHandler implements WorkHandler<ExpressDetailWmsProcessEvent> {
  22. private final RemoteBizExpressMonthDetailService remoteBizExpressMonthDetailService;
  23. private final WmsManager wmsManager;
  24. @Override
  25. public void onEvent(ExpressDetailWmsProcessEvent event) {
  26. //锁住信息,将状态更新为对接中
  27. BmsExpressMonthDetailDto express = event.getValue();
  28. R<String> result = remoteBizExpressMonthDetailService.updateCompanyByLogistics(express.getLogisticsNo(), ExpressStatusEnum.DOCKING.expressStatus, SecurityConstants.INNER);
  29. if(!result.isSuccess()){
  30. log.error("快递明细更新失败"+ JSONUtil.toJsonStr(express));
  31. event.setStop(true);
  32. return;
  33. }
  34. //将信息推送到wms
  35. ExpressWmsDto expressWmsDto = new ExpressWmsDto();
  36. BeanUtils.copyProperties(express,expressWmsDto);
  37. R<String> result2 = wmsManager.importExpress(expressWmsDto);
  38. if (!result2.isSuccess()) {
  39. event.setStop(true);
  40. log.info("快递信息推送WMS系统失败" + JSONUtil.toJsonStr(expressWmsDto));
  41. //更新快递状态为3(对接失败)
  42. remoteBizExpressMonthDetailService.updateCompanyByLogistics(express.getLogisticsNo(),ExpressStatusEnum.DOCKFAIL.expressStatus, SecurityConstants.INNER);
  43. return;
  44. }
  45. }
  46. }

api模块的构成

注意:

feign client中的请求url一定要和被请求接口在controller中的url保持一致

参数一定要设置正确

容错处理类要明确出错的原因,方便对异常的处理


本文转载自: https://blog.csdn.net/m0_72167535/article/details/127948633
版权归原作者 linab112 所有, 如有侵权,请联系我们删除。

“Spring Cloud中的Feign调用”的评论:

还没有评论