总结一下openfeign的用法:
分为三步:
1:引包导入
2:在启动类上加注解,启用feign. 可以扫包带有@FeignClient的,及可以设置它的配置类;
3:写feign接口,可以配置下feign接口的调用规则。
第一步引包,我这里用的是okhttp.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
第二步是在配置中加入注解启用。
@EnableFeignClients(basePackages = "com.gsafety.gemp.*.*.consumer.client", defaultConfiguration = {TokenFeignConfiguration.class})这里的TokenFeignConfiguration是一个配置类。
第三步写feign的配置类,
package com.macro.mall.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.macro.mall.common.api.CommonResult;
import com.macro.mall.common.exception.ApiException;
import com.qianda.mall.common.constant.Constants;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.apache.commons.lang3.StringUtils;
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.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author xay
* @version 1.0
* @since 2020/11/5
*/
@Configuration
@RestControllerAdvice
public class FeignConfig {
@Autowired
private ObjectMapper objectMapper;
/**
* 通用feign响应值解析器
*/
@Bean
public Decoder feignCommonDecoder() {
return (response, type) -> {
Response.Body body = response.body();
try (BufferedReader bufferedReader = new BufferedReader(body.asReader(Charset.forName("utf-8")));) {
String bodyStr = bufferedReader.lines().collect(Collectors.joining());
if (isDirectParse.test(type)) {
return objectMapper.readValue(bodyStr, TypeFactory.defaultInstance().constructType(type));
}
Type real;
if (type instanceof ParameterizedType) {
real = ((ParameterizedType) type).getRawType();
} else {
real = type;
}
JsonNode jsonNode = objectMapper.readTree(bodyStr);
int code = jsonNode.get(Constants.RESULT_CODE).intValue();
if (code != 200) {
throw new ApiException(jsonNode.get(Constants.RESULT_MSG).textValue());
}
String data = jsonNode.get(Constants.RESULT_DATA) == null ? "" : jsonNode.get(Constants.RESULT_DATA).toString();
if (StringUtils.isBlank(data)) {
if (real == Optional.class) {
return Optional.empty();
} else {
return null;
}
} else {
if (real == Optional.class) {
return Optional.of(objectMapper.readValue(data, TypeFactory.defaultInstance().constructType(((ParameterizedType) type).getActualTypeArguments()[0])));
} else {
return objectMapper.readValue(data, TypeFactory.defaultInstance().constructType(type));
}
}
}
};
}
@Bean
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
/**
* 可以带上请求头
* @return
*/
@Bean
RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
if (exclude_headers.contains(name.toLowerCase())) {
continue;
}
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
};
}
private static final List<String> exclude_headers = Arrays.asList("content-type","content-length");
private static Predicate<Type> isDirectParse = type -> type == CommonResult.class ||
(type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() == CommonResult.class);
/**
* Feign 客户端的日志记录,默认级别为NONE
* Logger.Level 的具体级别如下:
* NONE:不记录任何信息
* BASIC:仅记录请求方法、URL以及响应状态码和执行时间
* HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
* FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* Feign支持文件上传
* @param messageConverters
* @return
*/
@Bean
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@ExceptionHandler(value = DecodeException.class)
public CommonResult handleValidException(DecodeException e) {
Throwable cause = e.getCause();
if (cause != null && cause instanceof ApiException) {
return CommonResult.failed(e.getMessage());
}
e.printStackTrace();
return CommonResult.success("系统异常");
}
}
配置类写完了,可以写具体的feignClient的接口了。
import com.gsafety.gemp.app.duty.consumer.config.FeignMultipartConfig;
import com.gsafety.gemp.app.duty.consumer.fallback.AppAttachmentClientHystrix;
import com.gsafety.gemp.common.result.Result;
import com.gsafety.gemp.common.util.AttachmentOutDTO;
//import com.gsafety.gemp.standard.basicFacilities.CustomJSONBody;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@Component
@FeignClient(value = "gemp-file", path = "/api/attachment", fallback = AppAttachmentClientHystrix.class,configuration = FeignMultipartConfig.class)
public interface AppAttachmentClient {
/**
* 附件上传
*
* @param file
* @param module
* @return
*/
@PostMapping(value="/upload/v1",produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
AttachmentOutDTO upload(@RequestPart(value = "file") MultipartFile file,
@RequestParam(value = "module", required = false) String module);} //这个FeignClient中fallback与configuration是非必填的。
我这里可以写一个可以实现FeignClient接口的fallback类,用来处理融断。
@Component("AppAttachmentClientHystrix")
public class AppAttachmentClientHystrix implements AppAttachmentClient {
@Override
public AttachmentOutDTO upload(MultipartFile file, String module) {
throw new BusinessException("文件附件服务暂时不可用!");
}}
如果你的@FeignClient接口中没有fallback属性就可以不用写上面的Hystrix了。
我的ObjectMapper也贴出来参考下:
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 全局配置序列化返回 JSON 处理
SimpleModule simpleModule = new SimpleModule();
//JSON Long ==> String
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(local_datetime_formatter));
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(local_datetime_formatter));
simpleModule.addSerializer(BaseEnum.class, new JsonSerializer<BaseEnum>() {
@Override
public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject(value.getValue());
}
});
simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
/*
* TODO 类型条件限定过宽且仅做了名称处理
* */
simpleModule.addDeserializer(Enum.class, new CommonDeserializer());
objectMapper.registerModule(simpleModule);
return objectMapper;
}