Spring cloud OpenFeign项目集成(2)

446 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

如何统一处理业务响应操作失败的情况?来解决 大量调用者调用时进行业务是否成功的判断.

使用Feign中 Decoder功能

Decoder为解码器,用于将响应信息给反序列化为Java对象.使用什么样的序列化方式就是用对应的解码器. 解码器Feign内部实现使用装饰器+委派模式.

解码器分为全局和部分,配置方式与Request Interceptor配置类似.以下仅介绍配置文件中的配置.

public class DefaultDecoder implements Decoder {
		Decoder delegate; // 委派对象
  	public DefaultDecoder() {
        // 容器中获取解码器, 需要依赖一个配置类才能正确的获取到解码器
       // this.delegate = ApplicationContextUtils.getApplicationContext().getBean(Decoder.class);
    }
    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
       if (delegate == null) {
            // 不放在构造函数中,是因为在解析器创建时,spring 容器还未创建出来
            this.delegate = ApplicationContextUtils.getApplicationContext().getBean(Decoder.class);
        }
        Object o = delegate.decode(response, type);
       	DefaultResp resp = (DefaultResp)o;
        if (resp.getCode() != null && resp.getCode() == 0) {
          // 操作成功
          return o;
        } 
        // 操作失败,统一抛出异常
        throw new FeignClientException(resp.getCode(), resp.getMsg());
    }
}
feign:
  client:
    config:
      school:
        decoder: com.test.growth.school.feign.DefaultDecoder #解码器
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import feign.codec.Decoder;
import feign.optionals.OptionalDecoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * TODO
 *
 * Decoder decoder = null;
 *         try {
 *             decoder = ApplicationContextUtils.getApplicationContext().getBean(Decoder.class);
 *         } catch (NoSuchBeanDefinitionException e) {
 *             FeignClientsConfiguration configuration = ApplicationContextUtils.getApplicationContext().getBean(FeignClientsConfiguration.class);
 *             decoder = configuration.feignDecoder();
 */
@Configuration
public class CustomFeignClientsConfiguration extends FeignClientsConfiguration {

    @Resource
    private Jackson2ObjectMapperBuilder builder;

    /**
      * 默认解析器,
      *  将响应结果,下划线转驼峰
      *
      */
    @Bean
    public Decoder customFeignDecoder() {
        return new OptionalDecoder(
                new ResponseEntityDecoder(new SpringDecoder(this ::jacksonHttpMessageConverters)));
    }

    public HttpMessageConverters jacksonHttpMessageConverters() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = builder.build();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        converter.setObjectMapper(objectMapper);
        //设置中文编码格式
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(list);
        return new HttpMessageConverters(converter);
    }
}

基于解码器进行二次封装

image20210422113750956.png

抽象父类BusinessErrorDecoder,泛型为接口响应的统一数据类型,统一处理在业务响应失败的情况下抛出异常,新增一种数据类型,只需要2步: 1. 新建自己的ErrorDecoder继承BusinessErrorDecoder,实现其未实现的方法, 2. 配置解析器给哪个Client使用

import com.dahai.video.ApplicationContextUtils;
import com..growth.school.feign.FeignClientException;
import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;

import java.io.IOException;
import java.lang.reflect.Type;

/**
 * 业务错误-解析器
 */
public abstract class BusinessErrorDecoder<T> implements Decoder {
    protected Decoder delegate;

    public BusinessErrorDecoder() {

    }

    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        if (delegate == null) {
            // 不放在构造函数中,是因为在解析器创建时,spring 容器还未创建出来
            this.delegate = ApplicationContextUtils.getApplicationContext().getBean(Decoder.class);
        }
        Object o = delegate.decode(response, type);
        T t = (T) o;
        boolean flag = this.isBusinessError(t);
        if (flag) {
            throw new FeignClientException(this.getCode(t), this.getMsg(t));
        }
        return o;
    }
    /**
      * 判断业务码是否成功
      */
    protected abstract boolean isBusinessError(T obj);
    /**
      * 获取业务码
      
      */
    protected abstract Integer getCode(T obj);
    /**
      * 获取错误信息
      
      */
    protected abstract String getMsg(T obj);

}
import com.test.growth.school.feign.resp.DefaultResp;

/**
 * 解析
 */
public class DefaultDecoder extends BusinessErrorDecoder<DefaultResp> {

    @Override
    protected boolean isBusinessError(DefaultResp obj) {
        return obj.getCode() != null && obj.getCode() != 0;
    }

    @Override
    protected Integer getCode(DefaultResp obj) {
        return obj.getCode();
    }

    @Override
    protected String getMsg(DefaultResp obj) {
        return obj.getMsg();
    }

}
import com.dahai.video.common.exception.BusinessException;

/**
 * feign 客户端异常
 */
public class FeignClientException extends BusinessException {

    public FeignClientException(Integer code, String message) {
        super(code, message);
    }
}