欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Feign系列--全局异常处理

程序员文章站 2022-07-15 13:07:26
...

其他网址

Springboot2.x使用feign自定义Decoder,Advice_qq_33371766的博客-CSDN博客

简介

        之前介绍过feign的fallback,它可以出现异常时进行处理,使得代码出现问题能继续往下走,而不是直接报错。但这个的使用场景很少,一般都是出错直接抛异常的。

        本文介绍feign的全局异常处理,开发中更加常用。(基本与SpringMVC的全局异常相同,见:
SpringMVC系列--高级注解_feiying0canglang的博客-CSDN博客

公用代码

全局异常

package com.example.product.advice;
 
import com.example.product.entity.ResultWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
 
import javax.servlet.http.HttpServletRequest;

@Slf4j
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler({Exception.class})
    @ResponseBody
    public ResultWrapper handleException(HttpServletRequest request, Exception e) throws Exception {
//        e.printStackTrace();
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            throw e;
        }
        log.error("全局异常message:" + this.getClass().getPackage().getName() + e.getMessage());
        log.error("全局异常cause:" + this.getClass().getPackage().getName() + e.getCause());

        return ResultWrapper.failure()
                .message("全局异常message:" + this.getClass().getPackage().getName() + ":" + e.getMessage())
                .object("全局异常cause:" + this.getClass().getPackage().getName() + e.getCause());
    }
}

返回值包装类

package com.example.product.entity;

import lombok.Data;

@Data
public class ResultWrapper {
    private boolean success;

    private String message;

    private Object object;

    public ResultWrapper() {

    }

    public ResultWrapper(boolean success) {
        this.success = success;
    }

    public static ResultWrapper success(boolean success) {
        return new ResultWrapper(success);
    }

    public static ResultWrapper success() {
        return success(true);
    }

    public static ResultWrapper failure() {
        return success(false);
    }

    public <T extends ResultWrapper> T message(String message) {
        this.message = message;
        return (T) this;
    }

    public <T extends ResultWrapper> T object(Object object) {
        this.object = object;
        return (T) this;
    }

    public <T extends ResultWrapper> T object(Object... object) {
        this.object = object;
        return (T) this;
    }
}

用户实体类

package com.example.product.entity;

import lombok.Data;
import java.io.Serializable;

@Data
public class User {
    private Long id;
    private String userName;
    private Integer age;
}

调用端(Product)

feign配置

package com.example.product.config;

import com.example.product.feign.FeignResultWrapperDecoder;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Encoder feignEncoder() {
        return new SpringEncoder(messageConverters);
    }

    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new FeignResultWrapperDecoder(messageConverters));
    }

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

解码器

package com.example.product.feign;

import com.alibaba.fastjson.JSON;
import com.example.product.entity.ResultWrapper;
import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.HttpMessageConverterExtractor;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

@Slf4j
public class FeignResultWrapperDecoder implements Decoder {

    private ObjectFactory<HttpMessageConverters> messageConverters;

    public FeignResultWrapperDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        this.messageConverters = messageConverters;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        if (type instanceof Class || type instanceof ParameterizedType
                || type instanceof WildcardType) {
            if (type == ResultWrapper.class) {
                HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
                        type, this.messageConverters.getObject().getConverters());
                return extractor.extractData(new FeignResponseAdapter(response));
            } else {
                ResultWrapper wrapper = (ResultWrapper) decode(response, ResultWrapper.class);
                if (wrapper.isSuccess()) {
                    if (type == Object.class) {
                        return wrapper.getObject();
                    }
                    return JSON.parseObject(JSON.toJSONString(wrapper.getObject()), type);
                } else {
                    log.error("wrapper.getMessage():" + wrapper.getMessage());
                    throw new RuntimeException(wrapper.getMessage());
                }
            }
        }
        throw new DecodeException(response.status(),
                "type is not an instance of Class or ParameterizedType: " + type);
    }

    private final class FeignResponseAdapter implements ClientHttpResponse {

        private final Response response;

        private FeignResponseAdapter(Response response) {
            this.response = response;
        }

        @Override
        public HttpStatus getStatusCode() throws IOException {
            return HttpStatus.valueOf(this.response.status());
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return this.response.status();
        }

        @Override
        public String getStatusText() throws IOException {
            return this.response.reason();
        }

        @Override
        public void close() {
            try {
                this.response.body().close();
            } catch (IOException ex) {
                // Ignore exception on close...
            }
        }

        @Override
        public InputStream getBody() throws IOException {
            return this.response.body().asInputStream();
        }

        @Override
        public HttpHeaders getHeaders() {
            return getHttpHeaders(this.response.headers());
        }
    }

    private HttpHeaders getHttpHeaders(Map<String, Collection<String>> headers) {
        HttpHeaders httpHeaders = new HttpHeaders();
        for (Map.Entry<String, Collection<String>> entry : headers.entrySet()) {
            httpHeaders.put(entry.getKey(), new ArrayList<>(entry.getValue()));
        }
        return httpHeaders;
    }
}

feignclient

package com.example.product.feign;

import com.example.product.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//@FeignClient(name = "user", fallbackFactory = FeignFallbackFactory.class)
@FeignClient(name = "user")
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable("id") Long id);
}

ProductController 

package com.example.product.controller;

import com.example.product.entity.User;
import com.example.product.feign.UserFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    UserFeignClient userFeignClient;

    @GetMapping("/feign")
    public User testFeign(){
        User user = userFeignClient.getUser(1L);
        return user;
    }
}

被调用端(User)

package com.example.user.controller;

import com.example.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.logging.Logger;

//必须用这个,不能用@Controller。
@RestController
@RequestMapping("/user")
public class UserController {
    private Logger logger = Logger.getLogger(String.valueOf(getClass()));

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") Long id) {
        ServiceInstance serviceInstance = discoveryClient.getInstances("USER").get(0);
        logger.info("[" + serviceInstance.getServiceId() + "]:" +
                serviceInstance.getHost() + ":" + serviceInstance.getPort());
        User user = new User();
        user.setId(id);
        user.setUserName("user_name_" + id);
        user.setAge(20);
        int i = 1/0;
        return user;
    }
}

测试

postman访问:http://localhost:9002/product/feign

postman结果

{
    "success": false,
    "message": "全局异常message:com.example.product.advice:UserFeignClient#getUser(Long) failed and no fallback available.",
    "object": "全局异常cause:com.example.product.advicefeign.codec.DecodeException: 全局异常message:com.example.user.advice:/ by zero"
}

Product结果

2020-09-18 22:27:49.875 ERROR 4940 --- [ hystrix-user-1] c.e.p.feign.FeignResultWrapperDecoder    : wrapper.getMessage():全局异常message:com.example.user.advice:/ by zero
2020-09-18 22:27:49.886 ERROR 4940 --- [nio-9002-exec-1] c.e.product.advice.ExceptionAdvice       : 全局异常message:com.example.product.adviceUserFeignClient#getUser(Long) failed and no fallback available.
2020-09-18 22:27:49.886 ERROR 4940 --- [nio-9002-exec-1] c.e.product.advice.ExceptionAdvice       : 全局异常cause:com.example.product.advicefeign.codec.DecodeException: 全局异常message:com.example.user.advice:/ by zero

User结果

2020-09-18 22:27:49.840 ERROR 23736 --- [nio-8001-exec-1] com.example.user.advice.ExceptionAdvice  : 全局异常message:com.example.user.advice/ by zero
2020-09-18 22:27:49.840 ERROR 23736 --- [nio-8001-exec-1] com.example.user.advice.ExceptionAdvice  : 全局异常cause:com.example.user.advicenull

 

相关标签: 分布式