Feign源码学习

独自空忆成欢 提交于 2019-12-23 17:53:36

【推荐】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对象,然后调用ReflectFeignnewInstance方法创建动态代理,代码如下:

//默认使用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/
本人在其基础上加入自己的理解

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!