【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
feign介绍
Feign是一款java的Restful客户端组件,Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于Retrofit, JAXRS-2.0和WebSocket。feign在github上有近3K个star,是一款相当优秀的开源组件,虽然相比Retrofit的近30K个star,逊色了太多,但是spring cloud集成了feign,使得feign在java生态中比Retrofit使用的更加广泛。
feign的基本原理是在接口方法上加注解,定义rest请求,构造出接口的动态代理对象,然后通过调用接口方法就可以发送http请求,并且自动解析http响应为方法返回值,极大的简化了客户端调用rest api的代码。官网的示例如下:
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
static class Contributor {
String login;
int contributions;
}
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
feign使用教程请参考官网https://github.com/OpenFeign/feign/
本文主要是对feign源码进行分析,根据源码来理解feign的设计架构和内部实现技术。
Feign.build构建接口动态代理
我们先来看看接口的动态代理是如何构建出来的,下图是主要接口和类的类图:
从上文中的示例可以看到,构建的接口动态代理对象是通过Feign.builder()生成Feign.Builder的构造者对象,然后设置相关的参数,再调用target方法构造的。Feign.Builder的参数包括:
//拦截器,组装完RequestTemplate,发请求之前的拦截处理RequestTemplate
private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();
//日志级别
private Logger.Level logLevel = Logger.Level.NONE;
//契约模型,默认为Contract.Default,用户创建MethodMetadata,用spring cloud就是扩展这个实现springMVC注解
private Contract contract = new Contract.Default();
//客户端,默认为Client.Default,可以扩展ApacheHttpClient,OKHttpClient,RibbonClient等
private Client client = new Client.Default(null, null);
//重试设置,默认不设置
private Retryer retryer = new Retryer.Default();
//日志,可以接入Slf4j
private Logger logger = new NoOpLogger();
//编码器,用于body的编码
private Encoder encoder = new Encoder.Default();
//解码器,用户response的解码
private Decoder decoder = new Decoder.Default();
//用@QueryMap注解的参数编码器
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
//请求错误解码器
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
//参数配置,主要是超时时间之类的
private Options options = new Options();
//动态代理工厂
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
//是否decode404
private boolean decode404;
private boolean closeAfterDecode = true;
// 异常隔离策略
private ExceptionPropagationPolicy propagationPolicy = NONE;
这块是一个典型的构造者模式,target
方法内部先调用build
方法新建一个ReflectFeign
对象,然后调用ReflectFeign
的newInstance
方法创建动态代理,代码如下:
//默认使用HardCodedTarget
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(
client,
retryer,
requestInterceptors,
logger,
logLevel,
decode404,
closeAfterDecode);
ParseHandlersByName handlersByName = new ParseHandlersByName(contract,
options,
encoder,
decoder,
queryMapEncoder,
errorDecoder,
synchronousMethodHandlerFactory);
// handlersByName将所有参数进行封装,并提供解析接口方法的逻辑
// invocationHandlerFactory是Builder的属性,默认值是InvocationHandlerFactory.Default
// 用创建java动态代理的InvocationHandler实现
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
Target
源码如下:
public interface Target<T> {
// api 接口类
Class<T> type();
// 名称
String name();
// api接口路径的前缀地址(例如http://localhost:8080), 发送请求时会用这个地址和@RequestLine中的url路径(/user/list)
// 拼接在一起作为请求url(http://localhost:8080/user/list)
String url();
public Request apply(RequestTemplate input);
/**
* Target的默认实现,什么也不做
*/
public static class HardCodedTarget<T> implements Target<T> {
private final Class<T> type;
private final String name;
private final String url;
public HardCodedTarget(Class<T> type, String url) {
// 可以看到这里name默认和url一样
this(type, url, url);
}
public HardCodedTarget(Class<T> type, String name, String url) {
this.type = checkNotNull(type, "type");
this.name = checkNotNull(emptyToNull(name), "name");
this.url = checkNotNull(emptyToNull(url), "url");
}
@Override
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
input.target(url());
}
return input.request();
}
}
}
ReflectiveFeign
构造函数有三个参数:
ParseHandlersByName
将builder所有参数进行封装,并提供解析接口方法的逻辑InvocationHandlerFactory
java动态代理的InvocationHandler的工厂类,默认值是InvocationHandlerFactory.Default
QueryMapEncoder
接口参数注解@QueryMap
时,参数的编码器
ReflectiveFeign.newInstance
方法创建接口动态代理对象:
public <T> T newInstance(Target<T> target) {
//targetToHandlersByName是构造器传入的ParseHandlersByName对象,根据target对象生成MethodHandler映射
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//遍历接口所有方法,构建Method->MethodHandler的映射
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
//接口default方法的Handler,这类方法直接调用
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//这里factory是构造其中传入的,创建InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//java的动态代理
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
//将default方法直接绑定到动态代理上
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
文章绝大部分内容来源于:http://techblog.ppdai.com/2018/05/14/20180514/
本人在其基础上加入自己的理解
来源:oschina
链接:https://my.oschina.net/zhaopeng2012/blog/3146320