Openfeign使用教程(带你快速体验Openfeign的便捷)

文章摘要

本文中将教会您如何快速使用Openfeign,包括Opengfeign的基础配置、接口调用、接口重试、拦截器实现、记录接口日志信息到数据库


文章目录

  • 文章摘要
  • 一、Openfeign初步定义
  • 二、Openfeign快速入门
    • 1.引入maven坐标
    • 2.启动类增加@EnableFeignClients注解
    • 3.定义feign客户端并实现接口调用
    • 3.定义feign远程调用演示
  • 三、Openfeign常用配置
    • 1.配置请求连接超时
    • 2.配置Openfeign接口调用重试机制
    • 3.配置Openfeign请求拦截器
    • 4.Openfeign接口日志保存到数据库


一、Openfeign初步定义

OpenFeign 是一个基于 Spring 的声明式、模板化的 HTTP 客户端,它简化了编写 Web 服务客户端的过程。用户只需创建一个接口并添加相应的注解,即可实现对远程服务的调用。OpenFeignSpring Cloud 的一部分,它支持 Spring MVC 的注解,如 @RequestMapping,使得使用 HTTP 请求访问远程服务就像调用本地方法一样直观和易于维护。Openfeign底层默认使用JDK提供的HttpURLConnection进行通信(源码参考类feign.Default),使用Openfeign可以快速的帮我们完成第三方接口调用的实现,简化开发流程。

二、Openfeign快速入门

1.引入maven坐标

因为我使用的SpringBoot2.2.10.RELEASE版本,因此在引入Openfeign依赖时,也就引入与SpringBoot一致的版本,具体版本号根据您的SpringBoot版本来定。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

2.启动类增加@EnableFeignClients注解

启动类增加 @EnableFeignClients 用于在启动时加载我们定义的所有使用注解 @FeignClient 定义的feign客户端,并把feign客户端注册到 IOC 容器中。@EnableFeignClients注解中的basePackages用于配置扫描哪些包下来的类,这里我配置com.hl.by.remote包下的类
在这里插入图片描述
此时openfeign最基础的配置就已经配置完成了,现在可以自定义feign客户端,使用openfeign调用远程接口。


3.定义feign客户端并实现接口调用

feign的客户端需要使用 @FeignClient 注解进行标识,这样在扫描时才知道这是一个feign客户端。@FeignClient 最常用的就两个属性,一个是name,用于给客户端定义一个唯一的名称,另一个就是url,用于定义该客户端调用的远程地址。
在这里插入图片描述
这里的 ${open-feign.api-url} 是通过springBoot通过读取yaml或者properties文件获取的配置。这里我是在yaml中配置了该值:
在这里插入图片描述
因此客户端feign-remote-service调用的远程地址就是http://127.0.0.1:8080。在定义好客户端后,可以在客户端中加入对应的接口,比如我在客户端feign-remote-service中增加了一个saveSku的方法,当调用该方法时,将发送一个请求方式是POSTrequestBody的类型是FeignRemoteRequestVO,返回对象类型是ResponseResult,请求地址是http://127.0.0.1:8080/hl-template/mock/saveSku 的请求。
在这里插入图片描述

3.定义feign远程调用演示

由于我定义的feign客户端的调用地址是本地,因此我在本地定义了一个saveSku接口,用于演示feign远程调用的效果
在这里插入图片描述
我使用Postman 调用接口feign,然后通过feign这个接口使用刚刚定义的feign客户端进行远程调用saveSku方法。
在这里插入图片描述
在这里插入图片描述
通过feignRemoteService.saveSku将调用到第三方接口,可以看到已经通过feign客户端调用到了远程方法saveSku
在这里插入图片描述
现在您已经学会了使用Openfegn来进行最基础的远程调用。可以看出使用Openfeign以后,我们只用定义客户端即可,剩下的具体调用实现交给Openfeign就可以了。


三、Openfeign常用配置

本小节将对Openfeign常用配置进行演示,包括连接超时配置、接口调用、接口重试、拦截器实现,其中会涉及部分少量源码,但重点是教会您如何使用
Openfeign采用动态代理的方式实现接口调用,通过ReflectiveFeign.invoke 方法找到具体SynchronousMethodHandlerSynchronousMethodHandler是每个feign客户端中每个具体调用方法的最终实现。通过调用SynchronousMethodHandler中的invoke方法进行远程接口调用。

1.配置请求连接超时

Openfeign中常用的基础信息维护在抽象类Feign中,维护着Feign调用时的日志等级、客户端调用Client、重试机制、日志实现、请求编码器(encoder)、响应解码器(decoder)等。可以看到Feign采用Builder构建者模式对每个属性都设置了最初的值。而请求超时的配置则由类Options维护。
在这里插入图片描述Options类中属性只有三个,connectTimeoutMillis配置连接超时时间,readTimeoutMillis连接成功后等待响应超时时间,followRedirects是否允许重定向。默认的Options设置连接超时是10秒,等待响应超时是60秒,不允许重定向。
在这里插入图片描述
因此如果我们要自定义Openfeign的连接响应超时时间,则只需要自己声明一个Options注入到spring容器中去即可替换Openfeign默认的配置。我在yaml中配置了openfeign的接口连接超时信息,只需要创建一个配置类,读取配置信息放入Options中即可。
在这里插入图片描述
代码如下:

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {

    private Integer connectTimeoutMillis;
    private Integer readTimeoutMillis;
    private boolean followRedirects;
    private Integer maxAttempts;
    private Long period;
    private Long maxPeriod;

  /**
     * 配置openfeign调用接口时的超时时间和等待超时时间
     *
     * @return Request.Options
     */
    @Bean
    public Request.Options options() {
        return new Request.Options(connectTimeoutMillis, readTimeoutMillis, followRedirects);
    }

通过启动项目打断点就可以看到我们自定义的Options是否生效,可以看到我们自定的配置已经生效并设置到了Openfeign
在这里插入图片描述


2.配置Openfeign接口调用重试机制

Openfeign接口调用重试默认是由Retryer接口中的Default来实现,Default里默认设置每次接口调用失败随机休眠100毫秒~1000毫秒,最多重试5次。
在这里插入图片描述
在这里插入图片描述

如果我们要自定义Openfeign的重试机制的话,可以像Retryer.Default一样实现Retryer接口,这里我自定义了一个接口重试机制名叫FeignRetry,相比于Retryer.Default只是增加了错误信息打印。首先依然先在yaml配置文件中配置重试相关信息。配置了接口调用失败时休眠一秒进行重试,最多重试3次

open-feign:
  api-url: http://127.0.0.1:8088
  connectTimeoutMillis: 5000  #超时连接超时时间
  readTimeoutMillis: 30000 #连接成功后等待接口响应时间超时时间
  followRedirects: false #是否允许重定向
  maxAttempts: 3 #接口调用失败重试次数,调用失败通常指的是接口网络异常
  period: 1000 #接口调用失败后,周期多少毫秒后重新尝试调用
  maxPeriod: 1000 #接口调用失败后,最大周期多少毫秒后重新尝试调用

通过FeignConfig读取配置信息将配置信息加载到FeignRetry中:

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {

    private Integer connectTimeoutMillis;
    private Integer readTimeoutMillis;
    private boolean followRedirects;
    private Integer maxAttempts;
    private Long period;
    private Long maxPeriod;


    private final InterfaceLogMapper interfaceLogMapper;

    /**
     * 配置将重试N次,每次间隔M秒钟,重试条件为连接超时或请求失败
     *
     * @return Retryer
     */
    @Bean
    public Retryer feignRetryer() {
        return new FeignRetry(period, maxPeriod, maxAttempts);
    }

    /**
     * 配置openfeign调用接口时的超时时间和等待超时时间
     *
     * @return Request.Options
     */
    @Bean
    public Request.Options options() {
        return new Request.Options(connectTimeoutMillis, readTimeoutMillis, followRedirects);
    }

FeignRetry代码实现如下:

import feign.FeignException;
import feign.RetryableException;
import feign.Retryer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * @Author: Greyfus
 * @Create: 2024-03-10 00:56
 * @Version: 1.0.0
 * @Description:自定义feign请求时重试机制,与Default相比,只是增加了调用日志纪录机制
 */
public class FeignRetry implements Retryer {

    private static final Logger LOGGER = LoggerFactory.getLogger(FeignRetry.class);

    //最大重试次数
    private final int maxAttempts;

    //调用频率
    private final long period;

    //最大频率
    private final long maxPeriod;

    //尝试的次数
    int attempt;

    //纪录失眠次数
    long sleptForMillis;

    public FeignRetry() {
        this(1000, SECONDS.toMillis(1), 3);
    }

    public FeignRetry(long period, long maxPeriod, int maxAttempts) {
        this.period = period;
        this.maxPeriod = maxPeriod;
        this.maxAttempts = maxAttempts;
        this.attempt = 1;
    }


    protected long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public void continueOrPropagate(RetryableException e) {
        if (e instanceof FeignException) {
            //打印异常
            FeignException remoteException = e;
            LOGGER.error("Feign request【{}】 attempt 【{}】 times,status:【{}】,errorMessage:{}", remoteException.request().url(), attempt, remoteException.status(), remoteException.getMessage());
        }
        if (attempt++ >= maxAttempts) {
            throw e;
        }
        long interval;
        if (e.retryAfter() != null) {
            interval = e.retryAfter().getTime() - currentTimeMillis();
            if (interval > maxPeriod) {
                interval = maxPeriod;
            }
            if (interval < 0) {
                return;
            }
        } else {
            interval = nextMaxInterval();
        }
        try {
            Thread.sleep(interval);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            throw e;
        }
        sleptForMillis += interval;
    }


    long nextMaxInterval() {
        long interval = (long) (period * Math.pow(1.5, attempt - 1));
        return interval > maxPeriod ? maxPeriod : interval;
    }

    @Override
    public Retryer clone() {
        return new FeignRetry(period, maxPeriod, maxAttempts);
    }
}


3.配置Openfeign请求拦截器

openfeign提供了一个RequestInterceptor接口,凡是实现该接口的类并注入到Spring容器中后,在feign进行远程调用之前会调用实现该接口的所有类。源码可以参考SynchronousMethodHandler中的targetRequest方法,targetRequest是在执行远程调用之前进行调用。Openfeign请求拦截器的作用通常是用于设置公共属性,比如给请求设置请求头认证信息等。
在这里插入图片描述
这里我自定义了一个名叫FeignRequestInterceptor 的拦截器并使用@Component注入到容器中。当我调用远程接口时,通过debug模式可以看到该拦截器的apply方法被调用。

/**
 * @Author: DI.YIN
 * @Date: 2024/3/13 13:43
 * @Version: 1.0.0
 * @Description: openfeign请求拦截器,在发送请求前进行拦截
 **/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        System.out.println("你好");
    }
}

在这里插入图片描述


4.Openfeign接口日志保存到数据库

Openfeign提供了请求拦截器,但是没有提供响应拦截器,这导致我们无法纪录接口的响应信息到数据库。为了实现该功能我翻遍了SynchronousMethodHandler这个类的所有源码信息。我们先简单的对SynchronousMethodHandler源码进行分析,再来说如何实现将接口日志信息存入数据库。SynchronousMethodHandlerinvoke是整个openfeign远程调用了的核心部分,其最核心的是executeAndDecode方法,用于构建请求参数、请求方式等信息。
在这里插入图片描述
进入executeAndDecode方法后,第一个执行targetRequest方法,targetRequest就是循环遍历RequestInterceptor请求拦截器,调用每个RequestInterceptor实现类的apply方法。
在这里插入图片描述
在这里插入图片描述
执行完拦截器后,判断当前日志等级是否为不为NONE,如果当前日志等级不为NONE,则通过logger.logRequest打印日志。此时可以通过request获取到请求信息、请求地址、请求头等信息。
在这里插入图片描述
response = client.execute(request, options); 则是真正调用远程接口的地方,openfeing默认采用HttpURLConnection进行远程接口调用。如果调用异常,则logger.logIOException打印异常信息,如果调用成功,则通过logger.logAndRebufferResponse打印异常信息。
在这里插入图片描述
通过对源码的分析,我们知道当日志等级不为NONE时,openfeign会打印接口请求信息到控制台,如果接口调用失败,调用logIOException将错误信息打印到控制台,接口调用成功则调用logAndRebufferResponse将响应信息打印到控制台。因此我打算自定义一个日志对象,将接口信息存入数据库。


第一步创建接口日志信息表,用于存放接口日志信息:

CREATE TABLE interface_log (
log_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘日志主键’,
remote_sys_code varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘远程系统编码’,
remote_sys_name varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT ‘远程系统名称’,
request_time datetime NOT NULL COMMENT ‘请求时间’,
response_time datetime DEFAULT NULL COMMENT ‘响应时间’,
request_method varchar(10) COLLATE utf8mb4_bin NOT NULL COMMENT ‘请求方式 GET PUT POST’,
request_url varchar(200) COLLATE utf8mb4_bin NOT NULL COMMENT ‘请求地址’,
request_headers_message longtext COLLATE utf8mb4_bin COMMENT ‘请求头信息’,
request_message longtext COLLATE utf8mb4_bin COMMENT ‘请求信息’,
response_message longtext COLLATE utf8mb4_bin COMMENT ‘响应信息’,
status varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT ‘接口状态’,
PRIMARY KEY (log_id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT=‘日志记录表’;

第二步创建接口日志对象:

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hl.by.domain.base.BaseDomain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.util.Date;

/**
 * @Author: DI.YIN
 * @Date: 2024/3/13 16:05
 * @Version: 1.0.0
 * @Description: 日志实体类
 **/
@Data
@RequiredArgsConstructor
@TableName("interface_log")
public class InterfaceLogDomain extends BaseDomain {

    @TableField(exist = false)
    private static final long serialVersionUID = 2588400601329175428L;

    @ApiModelProperty(value = "日志主键")
    @TableId(type = IdType.AUTO)
    private Long logId;

    @ApiModelProperty(value = "远程系统编码")
    @TableField(value = "remote_sys_code")
    private String remoteSysCode;

    @ApiModelProperty(value = "远程系统名称")
    @TableField(value = "remote_sys_name")
    private String remoteSysName;

    @ApiModelProperty(value = "请求时间")
    @TableField(value = "request_time")
    private Date requestTime;

    @ApiModelProperty(value = "响应时间")
    @TableField(value = "response_time")
    private Date responseTime;

    @ApiModelProperty(value = "请求方式")
    @TableField(value = "request_method")
    private String requestMethod;

    @ApiModelProperty(value = "请求地址")
    @TableField(value = "request_url")
    private String requestUrl;

    @ApiModelProperty(value = "请求头内容")
    @TableField(value = "request_headers_message")
    private String requestHeaders;

    @ApiModelProperty(value = "请求内容")
    @TableField(value = "request_message")
    private String requestMessage;

    @ApiModelProperty(value = "响应内容")
    @TableField(value = "response_message")
    private String responseMessage;


    @ApiModelProperty(value = "接口状态")
    @TableField(value = "status")
    private String status;

}

第三步创建日志Mapper,包含对日志表的增删改查功能,这里我使用MyabtisPlus框架,因此实现相对简单。您可根据自己的实际情况实现Mapper。

/**
 * @Author: DI.YIN
 * @Date: 2024/3/13 16:34
 * @Version: 1.0.0
 * @Description: 接口日志
 **/
public interface InterfaceLogMapper extends BaseMapper<InterfaceLogDomain> {
}

第四步自定义日志对象FeignRemoteLogger

package com.hl.by.common.feign;

import com.hl.by.domain.log.InterfaceLogDomain;
import com.hl.by.domain.log.InterfaceLogStatus;
import com.hl.by.persistence.dao.log.InterfaceLogMapper;
import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.RequiredArgsConstructor;

import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;

/**
 * @Author: DI.YIN
 * @Date: 2024/3/13 15:36
 * @Version: 1.0.0
 * @Description: Feign接口远程调用接口日志记录
 **/
@RequiredArgsConstructor
public class FeignRemoteLogger extends Logger {

    private final InterfaceLogMapper logMapper;

    /**
     * 具体日志显示格式再次方法打印,因为要将日志写入数据库,因此该地方就不打印日志了
     *
     * @param configKey
     * @param format
     * @param args
     */
    @Override
    protected void log(String configKey, String format, Object... args) {

    }

    /**
     * 在调用之前调用该方法,将请求数据写入数据库
     *
     * @param configKey
     * @param logLevel
     * @param request
     */
    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {

        InterfaceLogDomain interfaceLogDomain = new InterfaceLogDomain();
        //从header中获取系统编码
        interfaceLogDomain.setRemoteSysCode(request.headers().get("SYS_CODE") == null || request.headers().get("SYS_CODE").isEmpty() ? "UNKNOWN" : request.headers().get("SYS_CODE").toArray()[0].toString());
        //从header中获取系统名称
        interfaceLogDomain.setRemoteSysName(request.headers().get("SYS_NAME") == null || request.headers().get("SYS_NAME").isEmpty() ? "UNKNOWN" : request.headers().get("SYS_NAME").toArray()[0].toString());
        interfaceLogDomain.setRequestTime(new Date());
        interfaceLogDomain.setStatus(InterfaceLogStatus.DOING.getCode());
        interfaceLogDomain.setRequestMethod(request.httpMethod().name());//请求方式
        interfaceLogDomain.setRequestUrl(request.url()); //请求地址
        Map<String, Collection<String>> headers = request.headers();//请求头
        if (request.requestBody() != null) {
            interfaceLogDomain.setRequestMessage(request.requestBody().asString());//请求内容
        }
        //存入数据库
        logMapper.insert(interfaceLogDomain);
        //放入线程缓存中,等待请求完成或者异常时,从线程缓存中拿出日志对象,回填数据
        FeignRequestContextHolder.setThreadLocalInterfaceLog(interfaceLogDomain);
    }

    /**
     * 当配置了接口重试机制时,再接口重试之前调用该方法
     *
     * @param configKey
     * @param logLevel
     */
    @Override
    protected void logRetry(String configKey, Level logLevel) {

    }

    /**
     * 当接口调用异常时,会调用该接口,如果配置了重试机制,该方法在logRetry之前调用
     *
     * @param configKey
     * @param logLevel
     * @param ioe
     * @param elapsedTime
     * @return
     */
    @Override
    protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {

        //从线程缓存中获取日志信息,用于信息回填存入数据库
        InterfaceLogDomain threadLocalInterfaceLog = FeignRequestContextHolder.getThreadLocalInterfaceLog();
        if (threadLocalInterfaceLog != null) {
            threadLocalInterfaceLog.setStatus(InterfaceLogStatus.ERROR.getCode());//回填响应状态
            threadLocalInterfaceLog.setResponseTime(new Date()); //回填响应时间
            threadLocalInterfaceLog.setResponseMessage(ioe.getMessage());
            try {
                logMapper.updateById(threadLocalInterfaceLog);
            } catch (Throwable throwable) {
                //清除线程缓存
                FeignRequestContextHolder.clear();
            }
        }
        return ioe;
    }

    /**
     * 接口调用成功后,调用该方法记录日志信息
     *
     * @param configKey
     * @param logLevel
     * @param response
     * @param elapsedTime
     * @return
     * @throws IOException
     */
    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        //从线程缓存中获取日志信息,用于信息回填存入数据库
        InterfaceLogDomain threadLocalInterfaceLog = FeignRequestContextHolder.getThreadLocalInterfaceLog();
        if (threadLocalInterfaceLog != null) {
            try {
                threadLocalInterfaceLog.setResponseTime(new Date());//回写接口响应时间
                threadLocalInterfaceLog.setStatus(InterfaceLogStatus.SUCCESS.getCode());//回写接口日志状态
                int status = response.status();
                int bodyLength;
                if (response.body() != null && !(status == 204 || status == 205)) {
                    // HTTP 204 No Content "...response MUST NOT include a message-body"
                    // HTTP 205 Reset Content "...response MUST NOT include an entity"
                    //获取响应内容
                    byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                    //内容长度
                    bodyLength = bodyData.length;
                    if (bodyLength > 0) {
                        String responseMessage = decodeOrDefault(bodyData, UTF_8, "Binary data");
                        threadLocalInterfaceLog.setResponseMessage(responseMessage);
                    }
                    return response.toBuilder().body(bodyData).build();
                }
            } finally {
                logMapper.updateById(threadLocalInterfaceLog);
                FeignRequestContextHolder.clear();
            }
        }
        return response;
    }
}

FeignRequestContextHolder 用于缓存每个线程当前所拥有的日志信息

/**
 * @Author: Greyfus
 * @Create: 2024/3/13 15:36
 * @Version: 1.0.0
 * @Description: 用于维护openfeign当前线程请求的请求日志对象,该对象在请求之前进行缓存,在请求结束或者异常后进行拿出回写日志信息保存到数据库
 */
package com.hl.by.common.feign;

import com.hl.by.domain.log.InterfaceLogDomain;

public class FeignRequestContextHolder {

    private static final ThreadLocal<InterfaceLogDomain> THREAD_LOCAL_INTERFACE_LOG = new ThreadLocal<>();

    /**
     * 获取当前线程缓存的日志信息
     *
     * @return
     */
    public static InterfaceLogDomain getThreadLocalInterfaceLog() {
        return THREAD_LOCAL_INTERFACE_LOG.get();
    }

    /**
     * 设置当前线程接口请求的日志信息
     *
     * @param interfaceLogDomain
     */
    public static void setThreadLocalInterfaceLog(InterfaceLogDomain interfaceLogDomain) {
        THREAD_LOCAL_INTERFACE_LOG.set(interfaceLogDomain);
    }

    /**
     * 清除数据
     */
    public static void clear() {
        THREAD_LOCAL_INTERFACE_LOG.remove();
    }
}

InterfaceLogStatus 枚举维护接口请求状态

package com.hl.by.domain.log;

/**
 * @Author: DI.YIN
 * @Date: 2024/3/13 16:31
 * @Version: 1.0.0
 * @Description: 接口日志状态
 **/
public enum InterfaceLogStatus {
    SUCCESS("S"),
    ERROR("E"),
    DOING("D"),
    ;
    private String code;

    InterfaceLogStatus(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

第五步设置日志等级并注入自定义日志对象

@Data
@Component
@ConfigurationProperties(prefix = "open-feign")
@RequiredArgsConstructor
public class FeignConfig {

    private Integer connectTimeoutMillis;
    private Integer readTimeoutMillis;
    private boolean followRedirects;
    private Integer maxAttempts;
    private Long period;
    private Long maxPeriod;

    @Autowired
    private InterfaceLogMapper interfaceLogMapper;

    /**
     * 参考SynchronousMethodHandler源码,executeAndDecode方法中,当日志等级不等于Logger.Level.NONE才触发日志显示
     *
     * @return 返回日志等级,openfeign根据日志等级来显示请求响应日志
     */
    @Bean
    public Logger.Level level() {
        return Logger.Level.FULL;
    }

    /**
     * 配置openfeign自定义请求日志记录,将请求日志存入MQ或者数据库
     *
     * @return
     */
    @Bean
    public Logger feignRemoteLogger() {
        return new FeignRemoteLogger(interfaceLogMapper);
    }
    
}

您仅需要将以上代码复制到您的项目中,就可以正常运行!!!现在让我们来测试一下效果。我用Postman调用本地接口,本地接口通过openfeign调用远程接口进行保存商品。
在这里插入图片描述
数据库的接口日志表已经成功保存本次请求的信息:
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/462397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从金蝶云星空到钉钉通过接口配置打通数据

从金蝶云星空到钉钉通过接口配置打通数据 对接系统金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&#xff0c;旨在帮助企业打造面…

IDEA连接Mysql失败:下载驱动失败,Failed todownload Cannot download Read timed out

解决&#xff1a; 1. 手动加入jar包 2.选择自己maven仓库中存在mysql-connector 3. 选择完毕后&#xff0c;确定使用&#xff1a; 4. 进行测试连接

【代码随想录】【回溯算法】补day24:组合问题以及组合的优化

回溯算法&#xff1a;递归函数里面嵌套着for循环 给定两个整数 n 和 k&#xff0c;返回 1 … n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 包含组合问题和组合问题的剪枝优化 class solution:def combine(se…

免费接口调用 招标信息自动抽取|招标信息|招标数据解析接口

一、开源项目介绍 一款多模态AI能力引擎&#xff0c;专注于提供自然语言处理&#xff08;NLP&#xff09;、情感分析、实体识别、图像识别与分类、OCR识别和语音识别等接口服务。该平台功能强大&#xff0c;支持本地化部署&#xff0c;并鼓励用户体验和开发者共同完善&#xf…

git:码云仓库提交以及Spring项目创建

git&#xff1a;码云仓库提交 1 前言 码云访问稳定性优于github&#xff0c;首先准备好码云的账户&#xff1a; 官网下载GIT&#xff0c;打开git bash&#xff1a; 查看当前用户的所有GIT仓库&#xff0c;需要查看全局的配置信息&#xff0c;使用如下命令&#xff1a; git …

旅游管理系统 |基于springboot框架+ Mysql+Java+Tomcat的旅游管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 摘要 研究…

羊大师分析羊奶入菜,美味新体验

羊大师分析羊奶入菜&#xff0c;美味新体验 羊奶&#xff0c;这一古老而珍贵的食材&#xff0c;近年来在料理界掀起了一股新风潮。其醇厚的口感和丰富的营养价值&#xff0c;让越来越多的人开始尝试将羊奶融入日常烹饪中&#xff0c;为味蕾带来前所未有的新体验。 在传统的烹饪…

由浅到深认识C语言(5):函数

该文章Github地址&#xff1a;https://github.com/AntonyCheng/c-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.csdn…

湖南麒麟SSH服务漏洞

针对湖南麒麟操作系统进行漏洞检测时&#xff0c;会报SSH漏洞风险提醒&#xff0c;具体如下&#xff1a; 针对这些漏洞&#xff0c;可以关闭SSH服务&#xff08;前提是应用已经部署完毕不再需要通过SSH远程访问传输文件的情况下&#xff0c;此时可以通过VNC远程登录方法&#x…

Arduino IDE配置ESP8266开发环境

一、配置步骤 在Arduino IDE中配置ESP8266开发环境的详细步骤如下&#xff1a; 1.打开Arduino IDE&#xff0c;依次点击“文件”->“首选项”&#xff0c;在“附加开发板管理器网址”一栏添加ESP8266开发板的网址。常用的网址是&#xff1a; http://arduino.esp8266.com/s…

软件测试——接口常见问题汇总

前言 今天我们来聊聊接口设计用例设计&#xff0c;说到这个接口&#xff0c;相信绝大多数的测试员都有遇到过某些棘手的问题&#xff0c;那么今天我们就来总结一下在接口方面会遇到的难题。 一、接口用例设计 接口测试用例可以从功能、性能、安全三方面进行入手&#xff0c;…

【好书推荐-第十二期】《并行计算与高性能计算》

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公众号&#xff1a;洲与AI。 &#x1f388; 本文专栏&#xff1a;本文收录…

[java基础揉碎]多态参数

多态参数 方法定义的形参类型为父类类型&#xff0c;实参类型允许为子类类型 例子: 定义一个员工类, 有名字和工资两个属性, 有年工资的方法 定义一个普通员工继承了员工类 , 重写了年工资的方法 定义一个经理类, 也继承了员工类, 同时经理多以了一个奖金的属性, 重写的年…

GPD<论文精简版>

问题陈述 给定点云数据、机械手的几何参数&#xff0c;抓取位姿检测问题&#xff08; grasp pose detection problem&#xff09;表示为&#xff0c;在抓手闭合情况下&#xff0c;识别抓手的配置的问题。 &#xff1a;机器人工作空间 &#xff1a;三维点云中的一组点&#x…

宠物疾病 与 光线疗法

人类与动物以及大自然是相辅相成的。人离开动物将无法生存&#xff0c;对于动物我们尽力去保护&#xff0c;与大自然和谐稳定生存发展。 生息在地球上的所有动物、在自然太阳光奇妙的作用下、生长发育。太阳光的能量使它们不断进化、繁衍种族。现在、生物能够生存、全仰仗于太…

windbg调试协议wireshark抓包解析插件

把目录下文件复制到如下位置,Wireshark支持版本4.0以上 C:\Program Files\Wireshark\plugins\4.0\kdnet.lua C:\Program Files\Wireshark\gcrypt.dll C:\Program Files\Wireshark\luagcrypt.dll 启动 “C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe” -k …

如何检测无源晶振过驱?晶振过驱怎么办?

无源晶振(Passive Crystal Oscillator)是一种使用晶体元件来生成稳定频率的振荡器&#xff0c;它不像有源振荡器(如时钟芯片)那样需要外部电源。检测无源晶振是否过驱通常需要通过测量其输出波形和频率&#xff0c;与期望的规格进行比较。 如何检测无源晶振过驱&#xff1a; …

Java高级互联网架构师之路:排查当前JVM错误的步骤

程序 这个程序是有问题的,我们通过一些命令来分析这个程序究竟是哪里出了问题。首先把当前的程序通过SSH工具传输到centos系统中,之后我们就可以在linux环境下编译和执行。 注意一点:上面类的名字是Z,但是在linux环境下,我们将其改为了AA,并且文件名改为了AA,所以文章下…

5 分钟小工具:使用 dive 分析 docker 镜像

需求 拿到一个镜像之后&#xff0c;我想知道&#xff1a; 分层查看镜像里都有哪些文件各层使用了什么命令构建的这个镜像镜像里比较大的文件有哪些&#xff08;可能需要优化&#xff09; dive 工具介绍 dive 工具可以做这些分析。dive 的 github 地址是 wagoodman/dive&…

2024年【电工(初级)】考试资料及电工(初级)实操考试视频

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 电工&#xff08;初级&#xff09;考试资料根据新电工&#xff08;初级&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将电工&#xff08;初级&#xff09;模拟考试试题进行汇编&#xff0c;组成一套电…