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.引入依赖

<!--服务调用-->
<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保持一致

参数一定要设置正确

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


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

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

还没有评论