SpringCloud系列--4.SpringCloud使用Feign
搭建EurekaServer
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.crazyit.test.cloud</groupId>
<artifactId>spring-feign-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
package org.crazyit.cloud;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ServerApplication.class).run(args);
}
}
搭建服务端
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.crazyit.test.cloud</groupId>
<artifactId>spring-feign-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
</project>
spring:
application:
name: spring-feign-provider
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
package org.crazyit.cloud;
public class Person {
private Integer id;
private String name;
private Integer age;
private String message;
public Person() {
super();
}
public Person(Integer id, String name, Integer age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
package org.crazyit.cloud;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FirstController {
@RequestMapping(value = "/person/{personId}", method = RequestMethod.GET)
public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) {
Person person = new Person(personId, "Crazyit", 30);
// 为了查看结果,将请求的URL设置到Person实例中
person.setMessage(request.getRequestURL().toString());
return person;
}
@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public String hello() {
return "Hello World";
}
}
package org.crazyit.cloud;
import java.util.Scanner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ProviderApplication {
public static void main(String[] args) {
// 读取控制台输入的端口,避免端口冲突
Scanner scan = new Scanner(System.in);
String port = scan.nextLine();
new SpringApplicationBuilder(ProviderApplication.class).properties(
"server.port=" + port).run(args);
}
}
搭建调用者
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.crazyit.test.cloud</groupId>
<artifactId>spring-feign-invoker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
</project>
启用Feign @EnableFeignClients
package org.crazyit.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class InvokerApplication {
public static void main(String[] args) {
SpringApplication.run(InvokerApplication.class, args);
}
}
定义@FeignClient("spring-feign-provider")
package org.crazyit.cloud;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("spring-feign-provider") //声明调用的服务名称
public interface PersonClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String hello();
@RequestMapping(method = RequestMethod.GET, value = "/person/{personId}")
Person getPerson(@PathVariable("personId") Integer personId);
}
Controller类
package org.crazyit.cloud;
import org.crazyit.cloud.contract.HelloClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Configuration
public class InvokerController {
@Autowired
private PersonClient personClient;
@RequestMapping(value = "/invokeHello", method = RequestMethod.GET)
public String invokeHello() {
return personClient.hello();
}
@RequestMapping(value = "/router", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String router() {
// 调用服务提供者的接口
Person p = personClient.getPerson(2);
return p.getMessage();
}
@Autowired
private HelloClient helloClient;
@RequestMapping(value = "/testContract", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String testContract() {
String springResult = helloClient.springHello();
System.out.println("使用 @RequestMapping 注解的接口返回结果:" + springResult);
String myResult = helloClient.myHello();
System.out.println("使用 @MyUrl 注解的接口返回结果:" + myResult);
return "";
}
/**
* 测试请求拦截器
*/
@RequestMapping(value = "/testInterceptors", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String testInterceptors() {
String springResult = helloClient.springHello();
return springResult;
}
}
自定义的注解以及拦截器如下:
@MyUrl.java
package org.crazyit.cloud.contract;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* 自定义的注解
* @author 杨恩雄
*
*/
@Target(METHOD)
@Retention(RUNTIME)
public @interface MyUrl {
// 定义url与method属性
String url();
String method();
}
package org.crazyit.cloud.contract;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* 客户端接口
* @author 杨恩雄
*
*/
@FeignClient(name = "spring-feign-provider")
public interface HelloClient {
@MyUrl(method = "GET", url = "/hello")
String myHello();
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String springHello();
}
package org.crazyit.cloud.contract;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Contract;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@Configuration
public class MyConfig {
/**
* 返回一个自定义的注解翻译器
*/
@Bean
public Contract feignContract() {
return new MyContract();
}
@Bean
public RequestInterceptor getRequestInterceptorsA() {
return new RequestInterceptor() {
public void apply(RequestTemplate template) {
System.out.println("这是第一个请求拦截器");
}
};
}
@Bean
public RequestInterceptor getRequestInterceptorsB() {
return new RequestInterceptor() {
public void apply(RequestTemplate template) {
System.out.println("这是第二个请求拦截器");
}
};
}
}
package org.crazyit.cloud.contract;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import feign.MethodMetadata;
/**
* 自定义Contract
* @author 杨恩雄
*/
public class MyContract extends SpringMvcContract {
/**
* 用于处理方法级的注解
*/
protected void processAnnotationOnMethod(MethodMetadata data,
Annotation annotation, Method method) {
// 调用父类的方法,吗时支持 @RequestMapping 注解
super.processAnnotationOnMethod(data, annotation, method);
// 是MyUrl注解才进行处理
if(MyUrl.class.isInstance(annotation)) {
// 获取注解的实例
MyUrl myUrlAnn = method.getAnnotation(MyUrl.class);
// 获取配置的HTTP方法
String httpMethod = myUrlAnn.method();
// 获取服务的url
String url = myUrlAnn.url();
// 将值设置到模板中
data.template().method(httpMethod);
data.template().append(url);
}
}
}
访问 http://localhost:9000/router
可以看到: 交替输出 8080 和 8081
访问: http://localhost:9000/testInterceptors
输出
这是第一个请求拦截器
这是第二个请求拦截器
访问 http://localhost:9000/testContract 输出:
这是第一个请求拦截器
这是第二个请求拦截器
使用 @RequestMapping 注解的接口返回结果:Hello World
这是第一个请求拦截器
这是第二个请求拦截器
使用 @MyUrl 注解的接口返回结果:Hello World
可见拦截器类 MyConfig.java
不管是对springCloud默认的Feign 还是对手写注解的@MyUrl都生效了。
SpringCloud为Feign提供了各种默认属性,以下的Bean
解码器 (Decoder): Bean的名称为: feignDecoder, ResponseEntityDecoder类。
编码器(Encoder): Bean的名称为: feignEncoder, SpringEncoder类.
日志 (Logger): Bean的名称为: feignLogger, Slf4jLogger类.
注解翻译器(Contract): Bean的名称为: feignContract, SpringMvcContract类.
Feign实例的创建者(Feign.Builder): Bean的名称为: feignBuilder, HystrixFeign.Builder类。
Feign客户端(Client): Bean的名称为 feignClient, LoadBalancerFeignClient类.
可选配置
以下配置spring并没有提供默认的Bean
- Logger.Level:接口日志的记录级别,相当于调用了Feign.Builder的logLevel方法。
- Retryer: 重试处理器,相当于调用了Feign.Builder的retryer方法
- ErrorDecoder: 异常解码器,相当于调用了Feign.Builder的errorDecoder方法
- Request.Options:设置请求的配置项,相当于调用了Feign.Builder的options方法
- Collection
:设置请求拦截器,相当于调用了Feign.Builder的requestInterceptors方法 - SetterFactory:该配置与Hystrix框架相关,
压缩配置
Feign支持对请求和响应进行压缩处理,默认使用GZIP进行压缩,压缩操作在Feign的请求拦截器中实现,可以在配置文件中加入以下配置:
- feign.compression.request.enabled: 设置为true开启请求压缩
- feign.compression.response.enabled:设置为true开启响应压缩.
- feign.compression.request.mime-types:数据类型列表,默认值为text/html,application/xml,application/json
- feign.compression.request.min-request-size:设置请求内容的最小阈值,默认为2048.
补充:
实际项目中可能自定义很多配置比如oauth权限认证,以及日志,解码器等等,示例如下:
使用类
package com.finup.mobile.client;
import com.finup.mobile.model.common.request.RequestVo;
import com.finup.mobile.vo.request.CommonCallRecordReqVo;
import com.finup.mobile.vo.request.CommonMsgRecordReqVo;
import com.finup.mobile.vo.request.CommonWxAddFriendReceiveVo;
import com.finup.mobile.vo.request.CommonWxAddFriendResultVo;
import com.finup.mobile.vo.request.CommonWxAgreeFriendResultVo;
import com.finup.mobile.vo.request.CommonWxCustomerAgreeFriendReceiveVo;
import com.finup.mobile.vo.request.CommonWxMsgRecordVo;
import com.finup.mobile.vo.response.chedai.CheDaiResult;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* 车贷service
*
*/
@FeignClient(name = "chedai", url = "${chedai.url}")
public interface CheDaiService {
/**
* 推送通话记录
* @param request CallRecordReqVo
* @return CheDaiResult
*/
@RequestMapping(value = "/xxx/v1/xxx", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
CheDaiResult<String> pushCallRecord(@RequestBody List<CommonCallRecordReqVo> request);
}
相关配置类如下:
package com.finup.mobile.config;
import feign.Feign;
import feign.InvocationHandlerFactory;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
import javax.validation.constraints.Size;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.UUID;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
/**
* FeiginClientAutoConfig
*/
@Slf4j
@Configuration
@ConditionalOnBean(OAuth2RestTemplate.class)
@EnableFeignClients(basePackages = "com.finup.mobile.client")
public class FeiginClientAutoConfig extends WebMvcConfigurerAdapter {
private static final String X_EVENT_HTTP_KEY = "x_event_http_key";
private static final String X_PATH = "x_path";
@Bean
public feign.Logger.Level feignLoggerLevel() {
return feign.Logger.Level.FULL;
}
@Resource(name = "renMaiRestTemplate")
private OAuth2RestTemplate oAuth2RestTemplate;
@Bean
public OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor() {
return new OAuth2FeignRequestInterceptor(oAuth2RestTemplate.getOAuth2ClientContext(),
oAuth2RestTemplate.getResource());
}
@Bean
public feign.Logger logTraceLogger() {
return new CustomFeignLogger();
}
@Bean
public Feign.Builder mdcLogBuilder() {
return new Feign.Builder().invocationHandlerFactory(invocationHandlerFactory()).errorDecoder(new PhoneErrorDecoder());
}
/**
* 添加spring mvc的拦截器,将来可以跟feign配置拦截器分离
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new EventHandlerInterceptor());
}
@Bean
public InvocationHandlerFactory invocationHandlerFactory() {
return (target, dispatch) -> (InvocationHandler) (proxy, method, args) -> {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0])
: null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
try {
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
MDC.put(X_PATH, methodMapping.value()[0]);
} catch (Exception ex) {
log.error("", ex);
}
String requestResponseKey = UUID.randomUUID().toString().replace("-", "");
MDC.put(X_EVENT_HTTP_KEY, requestResponseKey);
try {
return dispatch.get(method).invoke(args);
} finally {
MDC.remove(X_EVENT_HTTP_KEY);
}
};
}
}
package com.finup.mobile.config;
import feign.Request;
import feign.Response;
import feign.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.Formatter;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
public class CustomFeignLogger extends feign.Logger {
private final Logger logger;
public CustomFeignLogger() {
this(feign.Logger.class);
}
public CustomFeignLogger(Class<?> clazz) {
this(LoggerFactory.getLogger(clazz));
}
public CustomFeignLogger(String name) {
this(LoggerFactory.getLogger(name));
}
CustomFeignLogger(Logger logger) {
this.logger = logger;
}
/**
* Override to log requests and responses using your own implementation. Messages will be http
* request and response text.
*
* @param configKey
* @param format {@link Formatter format string}
* @param args arguments applied to {@code format}
*/
@Override
protected void log(String configKey, String format, Object... args) {
if (logger.isInfoEnabled()) {
logger.info(String.format(methodTag(configKey) + format, args));
}
}
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
if (logger.isInfoEnabled()) {
log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
int bodyLength = 0;
if (request.body() != null) {
bodyLength = request.body().length;
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
String
bodyText =
request.charset() != null ? new String(request.body(), request.charset()) : null;
log(configKey, "");
log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
}
}
log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
}
}
}
@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime)
throws IOException {
Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
for (String headerField : response.headers().keySet()) {
headers.put(headerField, response.headers().get(headerField));
}
if (logger.isInfoEnabled()) {
String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
" " + response.reason() : "";
int status = response.status();
log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
int bodyLength = 0;
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"
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
log(configKey, "");
}
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
bodyLength = bodyData.length;
if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
}
log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
return response.toBuilder().body(bodyData).build();
} else {
log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
}
}
return response;
} else {
return response;
}
}
}
package com.finup.mobile.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.finup.mobile.common.Constants;
import com.finup.mobile.model.RequestLogWithBLOBs;
import com.finup.mobile.model.common.daihou.request.DHReqBaseVo;
import com.finup.mobile.utils.DataUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
/**
* EventHandlerInterceptor
*/
@Slf4j
public class EventHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView){
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
}
package com.finup.mobile.config;
import feign.FeignException;
import feign.Response;
import feign.codec.ErrorDecoder;
public class PhoneErrorDecoder extends ErrorDecoder.Default {
public PhoneErrorDecoder() {
super();
}
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = super.decode(methodKey, response);
if (exception instanceof FeignException) {
exception = PhoneFeignException.errorStatus(methodKey, response);
}
return exception;
}
}
package com.finup.mobile.config;
import feign.FeignException;
import feign.Response;
import feign.Util;
import java.io.IOException;
import static java.lang.String.format;
public class PhoneFeignException extends FeignException {
protected PhoneFeignException(String message, Throwable cause) {
super(message, cause);
}
protected PhoneFeignException(String message) {
super(message);
}
protected PhoneFeignException(int status, String message) {
super(status, message);
}
public static FeignException errorStatus(String methodKey, Response response) {
String message = format("status %s reading %s url %s", response.status(), methodKey, response.request().url());
try {
if (response.body() != null) {
String body = Util.toString(response.body().asReader());
message += "; content:\n" + body;
}
} catch (IOException ignored) {
}
return new PhoneFeignException(response.status(), message);
}
}
标题:SpringCloud系列--4.SpringCloud使用Feign
作者:码农路上
地址:http://wujingjian.club/articles/2020/03/17/1584435979954.html