Feign的原理及源码解析

  • 前言
  • 使用示例
  • 原理解析
  • 注入原理解析
  • 小结
  • 调用原理解析
  • 小结

前言

本篇内容着重讲述Feign的调用源码,不阐述基本概念
版本使用:

spring-cloud-openfeign:2.2.8.RELEASE
springBoot:2.3.12.RELEASE

使用示例

在springBoot项目中,大家一般都是这么使用Feign的

①启动类贴注解

@SpringBootApplication
@EnableFeignClients
public class SkillBizApplication {
	public static void main(String[] args) {
		SpringApplication.run(SkillBizApplication.class, args);
	}
}

②定义Feign客户端

@FeignClient(name = "server1", fallbackFactory = TestServiceFallbackFactory.class)
public interface TestService {
    @GetMapping(value = "/test/{id}")
    String getUri(@PathVariable Long id);
}

③开始调用

@RestController
public class TestController {
    @Autowired
    private TestService testService;
    @GetMapping("/test")
    public String test(){
        return testService.getUri(11L);
    }
}

④断点调试

springboot feign设置hostip springboot feign原理_spring cloud

上面可以看到,这种调用方式比普通的方法多了两个注解,直接导致testService注入的其实是一个代理类.
EnableFeignClients和FeignClient,那么这两个注解到底做了啥,代理类到底是怎么代理的?

原理解析

注入原理解析

首先我们从EnableFeignClients注解入手

...
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

...
class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

可发现:
①EnableFeignClients注解导入了FeignClientsRegistrar类
②FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar
而实现了ImportBeanDefinitionRegistrar的类会在注册bean的时候自动执行registerBeanDefinitions方法

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		//1,注册EnableFeignCkients中配置到BeanDefinitionReistry中
		registerDefaultConfiguration(metadata, registry);
		//2,注册feign客户端到BeanDefinition中
		registerFeignClients(metadata, registry);
	}

方法1主要是注册配置类到容器中,略过

主要看下方法2

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		//---------------
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
		//上面那块的作用寻找FeignClient类
		//---------------------------------------------

		//循环取出FeignClient注解中的信息,如url,name等等
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(),
						"@FeignClient can only be specified on an interface");
				
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
				//开始注册到bean容器中
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

代码总结:
①扫描寻找贴了FeignClient的类
②取出FeignClient注解的元信息
③开始注册到bean容器中(registerFeignClient方法)

然后继续来看registerFeignClient方法的具体流程

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (ConfigurableBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
		//重点一,这里塞的是个工厂类
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(clazz, () -> {
					//重点2,下面这些set方法其实就是设置FeignClient注解的一些属性
					factoryBean.setUrl(getUrl(beanFactory, attributes));
					factoryBean.setPath(getPath(beanFactory, attributes));
					factoryBean.setDecode404(Boolean
							.parseBoolean(String.valueOf(attributes.get("decode404"))));
					Object fallback = attributes.get("fallback");
					if (fallback != null) {
						factoryBean.setFallback(fallback instanceof Class
								? (Class<?>) fallback
								: ClassUtils.resolveClassName(fallback.toString(), null));
					}
					Object fallbackFactory = attributes.get("fallbackFactory");
					if (fallbackFactory != null) {
						factoryBean.setFallbackFactory(fallbackFactory instanceof Class
								? (Class<?>) fallbackFactory
								: ClassUtils.resolveClassName(fallbackFactory.toString(),
										null));
					}
					//重点3,注意这里,最终取出来的对象是从factoryB
					return factoryBean.getObject();
				});
	.......
	}

这个方法主要作用:
构建了一个工厂bean,注意这里的factoryBean.getObject();属性注入时,就是从这里获取的feignClient实例,例如上面的TestController中的TestService属性

接下来看下factoryBean.getObject()

@Override
	public Object getObject() {
		return getTarget();
	}

	<T> T getTarget() {
		FeignContext context = beanFactory != null//客户端  上下文
				? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {//有配置url的话就用url,没有配置的话就是服务名
			if (url != null && LOG.isWarnEnabled()) {
				LOG.warn(
						"The provided URL is empty. Will try picking an instance via load-balancing.");
			}
			else if (LOG.isDebugEnabled()) {
				LOG.debug("URL not provided. Will use LoadBalancer.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context,//获取fein客户端实例对象
					new HardCodedTarget<>(type, name, url));
		}
		.........

可以看到这个地方传入了一个HardCodedTarget对象,可以发现这个对象就是上面TestService的代理对象

然后看下loadBalance方法

springboot feign设置hostip springboot feign原理_客户端_02


这里可以看到有一个hystrixTarget,然后看下对应的target方法

springboot feign设置hostip springboot feign原理_java_03

因为我配置过fallbackFactory,所以会走到targetWithFallbackFactory方法中

springboot feign设置hostip springboot feign原理_客户端_04


继续看看build.target方法

springboot feign设置hostip springboot feign原理_java_05

再看下buid做了啥

springboot feign设置hostip springboot feign原理_分布式_06


这里最主要的是复写了create方法,返回了一个HystrixInvocationHandler实例,大家注意下这个类,下面会用到,然后我们回过头再看下build(fallbackFactory).newInstance(target);

public <T> T newInstance(Target<T> target) {
 		//这个是封装MethodHandler,key是方法名(例如getUri)
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }
		//这里就会调用上面复写的create方法,设置了dispatch参数
        InvocationHandler handler = this.factory.create(target, methodToHandler);
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }

        return proxy;
    }

这里主要就是封装了代理对象,给大家看看现在的proxy对象

springboot feign设置hostip springboot feign原理_分布式_07


可以发现和我们最开始注入的对象是一样的结构,ok这里关于怎么注入的feign就基本介绍完了

小结

①使用EnableFeignClients注解导入FeignClientsRegistrar类,在注册bean的时候调用registerBeanDefinitions方法
②registerBeanDefinitions方法寻找贴了FeignClient注解的类并解析,最后把复写了工厂类的getObject方法,并把FeignClientFactoryBean注册到bean工厂
③属性注入的时候会从bean容器中获取对应的feign客户端,这时会调用factoryBean.getObject();
④获取实例的过程中生成一个代理类(HystrixInvocationHandler)注入到最终使用的地方

调用原理解析

通过上面我们可以知道注入的代理类是HystrixInvocationHandler,那么当我们调用方法时,会调用HystrixInvocationHandler的invoke方法,如下

springboot feign设置hostip springboot feign原理_ide_08


注意重点HystrixInvocationHandler.this.dispatch.get(method).invoke(args)

这里的dispatch就是我们之前注入进去的,这里获取到的对象是SynchronousMethodHandler,然后我们看一下SynchronousMethodHandler的invoke方法

public Object invoke(Object[] argv) throws Throwable {
.....
        while(true) {
            try {
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;
                .....
    }

再看下executeAndDecode方法

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
      .....
        Response response;
        try {
            response = this.client.execute(request, options);
 		...
        }

主要看下这个执行的execute方法,因为feign集成了ribbon,这里的client是LoadBalancerFeignClient,看下execute方法

public Response execute(Request request, Request.Options options) throws IOException {
		try {
			URI asUri = URI.create(request.url());
			String clientName = asUri.getHost();
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			//主要看这行
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

这里注意IClientConfig requestConfig = getClientConfig(options, clientName)方法 在这个里面会去加载ribbon的负载的相关配置,以及需要用到的服务实例列表都封装在了IClientConfig 里面

然后这里是通过调用executeWithLoadBalancer方法来调用的

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();

这里的command.submit方法其实就是调用ribbon负载均衡来选择一个服务,如下

public Observable<T> submit(final ServerOperation<T> operation) {
       .....
        // Use the load balancer
        Observable<T> o = 
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    @Override
                    // Called for each server being selected
                    public Observable<T> call(Server server) {
                        context.setServer(server);
                        final ServerStats stats = loadBalancerContext.getServerStats(server);
                        
                        // Called for each attempt and retry

主要是selectServer方法

private Observable<Server> selectServer() {
        return Observable.create(new OnSubscribe<Server>() {
            @Override
            public void call(Subscriber<? super Server> next) {
                try {
                    Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                    next.onNext(server);
                    next.onCompleted();
                } catch (Exception e) {
                    next.onError(e);
                }
            }
        });
    }

然后在getServerFromLoadBalancer方法中通过负载策略获取对应的server
获取到对应的server后通过上面的call方法来执行请求,从而完成远程调用

小结

①调用feign代理类的方法时,会调用HystrixInvocationHandler的invoke方法
②接下来会调用SynchronousMethodHandler的invoke方法
③执行到LoadBalancerFeignClient的execute方法
④执行AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法
⑤通过LoadBalancerCommand的submit取出一个服务来
⑥通过http请求这个服务,获取结果,完成一次远程调用