引言
Spring Cloud OpenFeign 用于以声明式快速构建Restful API,它简化了与HTTP服务交互的过程,使得接口调用更加方便和简洁,是构建微服务架构的重要工具。本文将详细阐述如何构建一个健全,简洁,可靠的rest接口。
引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
定义接口
- 服务间调用
@FeignClient(contextId = "intr", value = "target-service")
public interface Intr {
@PostMapping("/api/v1/test")
Response call(@RequestBody Request request);
}
- 固定地址调用
@FeignClient(name = "intr", url = "http://127.0.0.1:8080")
public interface Intr {
@PostMapping("/api/v1/test")
Response call(@RequestBody Request request);
}
- 动态地址调用
@FeignClient(name = "intr", url = "dynamic")
public interface Intr {
@PostMapping("/api/v1/test")
Response call(URI uri, @RequestBody Request request);
}
做客户端
@Service
public class Client {
@Autowired
private Intr intr;
public Response doCall() {
return intr.call(new Request());
}
}
做服务端
@RestController
public class Server implements Intr{
@Override
public Response call(Request request) {
return new Response();
}
}
对象定义
Spring Cloud OpenFeign 支持多种类型的请求和响应对象,这些对象可以是基本类型、集合或自定义类型
- 枚举
支持在对象中使用枚举,如果枚举值与实际值不符,则需要单独标注映射值
@Data
public class Response {
private ResEnum code;
}
使用Jackson(默认):
public enum ResEnum {
/**
* 成功
*/
succ(1),
/**
* 失败
*/
fail(0);
@JsonValue
public final int code;
}
使用FastJson:
public enum ResEnum {
/**
* 成功
*/
succ(1),
/**
* 失败
*/
fail(0);
@JSONField
public final int code;
}
- 时间格式
使用Jackson(默认):
public class Response {
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date timestamp;
}
使用FastJson:
public class Response {
@JSONField(pattern="yyyy-MM-dd HH:mm:ss")
private Date timestamp;
}
编码/解码
openFeign默认使用Jackson,当遇到复杂对象或是使用集合包装的对象,可能会出现解码异常, 推荐改为Fastjson做为编码/解析器
@Bean
public ResponseEntityDecoder feignDecoder() {
HttpMessageConverter<?> fastJsonConverter = createFastJsonConverter();
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(fastJsonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
@Bean
public SpringEncoder feignEncoder() {
HttpMessageConverter<?> fastJsonConverter = createFastJsonConverter();
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(fastJsonConverter);
return new SpringEncoder(objectFactory);
}
private HttpMessageConverter<?> createFastJsonConverter() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
fastConverter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue
);
fastConverter.setFastJsonConfig(fastJsonConfig);
return fastConverter;
}
异常处理
- 客户端
指定接口:
public class MyErrorDecoder extends ErrorDecoder.Default {
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = super.decode(methodKey, response);
switch (response.status()) {
case 401:
return new IllegalStateException("鉴权认证失败", exception);
case 404:
return new IllegalStateException("地址错误", exception);
case 503:
return new IllegalStateException("服务不可用", exception);
default:
return new IllegalStateException("请求失败", exception);
}
}
}
@FeignClient(contextId = "intr", value = "target-service" , configuration = MyErrorDecoder.class)
public interface Intr {
@PostMapping("/api/v1/test")
Response call(@RequestBody Request request);
}
全局定义:
@Bean
public ErrorDecoder myErrorDecoder() {
return (key, response) -> {
switch (response.status()) {
case 401:
return new IllegalAccessException("鉴权认证失败", exception);
case 404:
return new IllegalArgumentException("地址错误", exception);
default:
return new RuntimeException("请求失败", exception);
}
};
}
- 服务端
@RestControllerAdvice
public class ErrorHandler {
@ExceptionHandler(IllegalAccessException.class)
public ResponseEntity<?> handleAccessException(IllegalAccessException exception) {
return ResponseEntity.status(401).build();
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleArgumentException(IllegalArgumentException exception) {
return ResponseEntity.status(404).build();
}
}
HTTPS
- 单向认证
客户端:
@Value("${server.ssl.trust-store}")
private String trustStorePath;
@Value("${server.ssl.trust-store-password}")
private String trustStorePassword;
@Bean
public SSLContext sslContext() throws Exception {
return SSLContextBuilder.
create().
loadTrustMaterial(ResourceUtils.getFile(trustStorePath), trustStorePassword.toCharArray()).
build();
}
@Bean
public Client feignClient(SSLContext sslContext) {
return new Client.Default(sslContext.getSocketFactory(), (hostname, session) -> true);
}
#application.yml
server:
ssl:
enabled: false
trust-store: classpath:truststore.jks
trust-store-type: JKS
trust-store-password: 123456
服务端:
#application.yml
server:
ssl:
key-store: classpath:keystore.jks
key-store-type: JKS
key-store-password: 123456
- 双向认证
客户端/服务端:
Feign配置同单向认证
//application.yml
server:
ssl:
key-store: classpath:keystore.jks
key-store-type: JKS
key-store-password: 123456
trust-store: classpath:truststore.jks
trust-store-type: JKS
trust-store-password: 123456
绕过认证:
@Bean
public Client feignClient() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true);
}
日志级别
- 内部配置
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
- 外部配置
#application.yml
feign:
client:
config:
default:
loggerLevel: basic
超时
- connectTimeout:指客户端连接服务端的超时时间
- readTimeout:指客户端连接完成后,等待服务端响应结果的超时时间
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 10000
开启压缩
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml, application/json
min-request-size: 1024
response:
enabled: true
okhttp3
OkHttp3 是一个 HTTP 客户端库,它可以更高效地处理网络请求
- 导入
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
- 开启
feign:
okhttp:
enabled: true
- 自定义
@Bean
public OkHttpClient feignClient() {
return new OkHttpClient.Builder()
//设置连接超时
.connectTimeout(10, TimeUnit.SECONDS)
//设置读超时
.readTimeout(10, TimeUnit.SECONDS)
//设置写超时
.writeTimeout(10, TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
//设置连接池
.connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES))
.build();
}