一、sweet-boot-feign-starter是什么?
sweet-boot-feign-starter 是基于spring-cloud-starter-openfeign二次封装的组件,是sweet-boot中重要的一个启动装置。方便应用程序与feign的快速集成,整合feign-okhttp和nacos来为使用feign时提供负载均衡。
二、封装
1.创建sweet-boot-feign-starter模块,编辑pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>com.lyd.sweet</groupId>
<artifactId>sweet-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>sweet-boot-feign-starter</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.lyd.sweet</groupId>
<artifactId>sweet-boot-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!--让feign使用okhttp作为httpclient实现-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<!--利用HttpServletRequest设置header-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.创建Feign配置类
/**
* Feign的自动配置。
* 约定FeignClient的定义统一放在“com.”目录下任一个目录,自动配置扫描该路径。
* 默认情况下Feign重试机制为关闭,如需要开启重试策略,扩展此配置类增加设置。
* 定义feign响应结果解码器
* 通过spring.factories自动注册
*
* @author: 木木
*/
@Configuration
@EnableFeignClients(basePackages = {"com.**"})
public class FeignConfig {
/**
* feign的顶层过滤器
*
* @return
*/
@Bean
public RequestInterceptor requestInterceptor() {
return new FeignInterceptor();
}
/**
* feign响应结果适配器是否开启,默认开启
*
* @param messageConverters
* @return
*/
@Bean
@ConditionalOnProperty(name = "sweet.feign.response.adapter.enabled", havingValue = "true", matchIfMissing = true)
public Decoder feignDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new OptionalDecoder((new ResponseEntityDecoder(new FeignSuccessDecoder(messageConverters))));
}
/**
* feign异常消息解码器
*
* @return
*/
@Bean
public ErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
}
3.创建Feign的顶层过滤器
/**
* feign的顶层过滤器。用于在使用feign进行内部微服务之间请求时统一添加token信息。
*
* 如果当前微服务应用的token,当微服务作为服务消费者调用其他微服务时,默认将token通过header传递。
*
* 如果是基于feign调用外部第三方restapi时需要通过header传递token,
* 则根据对方要求在feign方法定义处通过@RequestHeader注解传递token,将会覆盖本类默认设置的内容。
*
* @author 木木
*/
public class FeignInterceptor implements RequestInterceptor {
/**
* 内部标识,内容调用不二次封装head头
*/
public static final String FEIGN_HEADER = "FEIGN-HEADER";
/**
* 内部标识值
*/
public static final String FEIGN_HEADER_VALUE = "SweetFeign";
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null != attributes) {
HttpServletRequest request = attributes.getRequest();
//取token的值,根据spring security约定token会放在header中或者通过请求参数“access_token”传递。
//约定默认将token放在header中。因此优先从header中取,如果没有则通过请求参数access_token取
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
if (token == null || token.isEmpty()) {
token = request.getParameter("access_token");
}
if (token != null && !token.isEmpty()) {
//统一通过header传token。spring security默认会从header取token。
requestTemplate.header(HttpHeaders.AUTHORIZATION, token);
}
// 默认在header中加一个标识。用于识别服务消费者是否来自feign的调用,
// 从而判断服务调用是来自内部微服务间的内部调用还是外部第三方的调用。
requestTemplate.header(FEIGN_HEADER, FEIGN_HEADER_VALUE);
}
}
}
4.创建Feign响应结果解析器
/**
* feign响应结果解析器
* @author 木木
*/
@Slf4j
public class FeignSuccessDecoder extends SpringDecoder {
public FeignSuccessDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
super(messageConverters);
}
/**
* 使用sweet boot开发的服务间使用Feign调用时,根据feignClient定义的接口ReturnType将响应结果解析为对应的对象
* {@link ResponseObject}
*
* @param response Feign响应
* @param type {@link Type}
* @return {@link Type} result
* @throws IOException
* @throws FeignException
*/
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
log.debug("FeignSuccessDecoder.decode.param.typeName:{}", type.getTypeName());
boolean isReturnResponseObjectType = type.getTypeName().startsWith(ResponseObject.class.getTypeName());
Map<String, Collection<String>> resHeaders = response.headers();
Collection<String> resIsResponseObjects = resHeaders.get("IsResponseObject");
String isResponseObjectStr = resIsResponseObjects.stream().findFirst().orElse("false");
boolean isResponseObject = "true".equals(isResponseObjectStr);
//方法返回类型为ResponseObject
if (isReturnResponseObjectType) {
//结果非ResponseObject
if (!isResponseObject) {
Object value = super.decode(response, Object.class);
return ResponseObject.success(value);
}
return super.decode(response, type);
}
//结果为ResponseObject,方法返回类型非ResponseObject
if (isResponseObject) {
ResponseObject<?> result = (ResponseObject<?>) super.decode(response, new ResponseObjectParameterizedType(new Type[]{type}));
//只返回data
return result.getData();
}
//结果非ResponseObject,方法返回类型非ResponseObject
return super.decode(response, type);
}
/**
* 定义一个ResponseObject结构类型
* {@link ResponseObject}
*/
static final class ResponseObjectParameterizedType implements ParameterizedType {
private final Type[] actualTypeArguments;
ResponseObjectParameterizedType(Type[] actualTypeArguments) {
this.actualTypeArguments = actualTypeArguments;
}
public Type[] getActualTypeArguments() {
return this.actualTypeArguments;
}
public Type getRawType() {
return ResponseObject.class;
}
public Type getOwnerType() {
return null;
}
}
}
5.创建Feign响应异常解析器
/**
* Feign统一异常处理解码器
* 用于处理SweetBoot定义的标准业务异常返回消息
* @author 木木
*/
@Slf4j
public class FeignErrorDecoder extends ErrorDecoder.Default {
private final String HEAD_KEY = "head";
private final String CODE_KEY = "code";
private final String MESSAGE_KEY = "message";
@Override
public Exception decode(String methodKey, Response response) {
try {
String bodyJson = Util.toString(response.body().asReader(StandardCharsets.UTF_8));
JacksonJsonParser jacksonJsonParser = new JacksonJsonParser();
Map<String, Object> errorInfo = jacksonJsonParser.parseMap(bodyJson);
// 目前根据返回的JSON字符串 判断是否带head字段 如果带head字段说明是SweetBoot定义的标准业务返回(ResponseObject)
// 解析JSON字符串 构建 SweetBasicException对象 返回给调用的服务
if (errorInfo.containsKey(HEAD_KEY)) {
Map<String, Object> headMap = (Map<String, Object>) errorInfo.get(HEAD_KEY);
String code = headMap.get(CODE_KEY).toString();
String message = headMap.get(MESSAGE_KEY).toString();
ServiceStatus serviceStatus = new ServiceStatus(code, message);
return new SweetBasicException(serviceStatus, message);
} else {
// 如果不是SweetBasicException 则用默认异常类包装信息返回
return super.decode(methodKey, response);
}
} catch (Exception e) {
log.warn(e.getMessage());
return super.decode(methodKey, response);
}
}
}
三、使用步骤
1.引入库
<dependency>
<groupId>com.lyd.sweet</groupId>
<artifactId>sweet-boot-feign-starter</artifactId>
</dependency>
2.示例
sweet-boot-feign-starter: 使用示例。 - Gitee.com