zl程序教程

您现在的位置是:首页 >  其它

当前栏目

OpenFeign,声明式的伪RPC调用(一)

调用 声明 RPC OpenFeign
2023-09-14 09:15:23 时间

一、前言

二、OpenFeign实现声明式的伪RPC调用

新建两个工程,spring-cloud-order-service作为服务提供方,spring-cloud-user-service作为服务使用方。

在这里插入图片描述

在这里插入图片描述

现在我们要在spring-cloud-user-service中来访问这个,直接用httpurlconnection来访问,也可以用restTemplate来访问,但是这里要用openfeign来访问。

注意,无论是restTemplate还是openFeign,底层都是用http来访问的。

先导入pom依赖,如下:

在这里插入图片描述

修改配置文件application.properties,如下:

在这里插入图片描述

定义一个FeignClient,如下:

在这里插入图片描述

服务使用方这边定义一个服务,如下:

在这里插入图片描述

springboot启动类加上注解,如下:

在这里插入图片描述

启动,并访问 localhost:8088/testFeign

在这里插入图片描述

Feign就是在spring-cloud-user-service服务使用端调用到服务提供方

好了,没了,就是这么简单,重点是OpenFeign是怎么用自己的四要素实现封装http请求的,看一下源码解析。

三、OpenFeign源码解析

3.1 OpenFeign源码需要做哪些事情

从httpclient(或jdk httpurlConnection)到restTemplate再到openFeign,整个过程都是不断在包装,在封装,其实底层还是http通信。

现在我们分析,需要确定一个http通信要哪些必备要素,需要:

  1. 请求路径:“ip+port+子路径”构成的url;
  2. 请求方式:method type,是get还是post;
  3. 请求参数:parameter
  4. MediaType(MIMETYPE):json、html、text,一般默认json。

那么,在feign中,我们提供了四要素:服务名称(feign默认集成ribbon将服务名称解析为ip:port,然后拼接子路径)、请求方式、请求参数、返回结果。

ribbon完成“服务名称解析为ip:port + 负载均衡”两件事情,如:
order-service ip:port
[{
/orders,method=GET,
/order,method=POST
}]
参数:OrderDao var1

我们看到,feign四要素完全可以构成一个http请求需要是实参,所以,我们现在深入openfeign源码来处理看看feign是如何完成这个过程的。

feign源码中要完成的事情,如下:
1、参数的解析和装载;
2、针对指定的feignClient,生成动态代理;
3、针对FeignClient中的方法描述进行动态解析;
4、最后,组装成一个request对象发起请求。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

好了,现在我们来看看,OpenFeign是怎样完成这些工作的。

3.2 从@EnableFeignClients开始

从@EnableFeignClients开始,这个注解是开启对@FeignClients注解的扫描,

在这里插入图片描述

我们知道,Spring中的Bean动态装载涉及两种注解:ImportSelector和ImportBeanDefinitionRegistrar,而且现在,这个FeignClientsRegistrar就是ImportBeanDefinitionRegistrar接口的实现类,且看:

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

什么,不清楚ImportBeanDefinitionRegistrar接口是如何实现自动装配bean的,好的,我们自己动手来写一个ImportBeanDefinitionRegistrar接口的实现类,看一看。

在这里插入图片描述

开始动手了。

第一步,先新建一个HelloService类,表示要装载到Spring容器中的bean,如下:

在这里插入图片描述

第二步,

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以,ImportBeanDefinitionRegistrar和ImportSelector是一样的,都是spring启动可以执行到的。

好了,回到feign的源码,spring启动的时候也一定会执行到FeignClientsRegistrar类的registerBeanDefinitions()方法,那个这个方法里面执行了什么呢,我们看到,就是

registerDefaultConfiguration(metadata, registry);   // 注册默认配置信息
registerFeignClients(metadata, registry);  // 注册Feign的所有客户,这个重点

在这里插入图片描述

public void registerFeignClients(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	ClassPathScanningCandidateComponentProvider scanner = getScanner();
	scanner.setResourceLoader(this.resourceLoader);

	Set<String> basePackages;

	Map<String, Object> attrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName());
	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
			FeignClient.class);
	final Class<?>[] clients = attrs == null ? null
			: (Class<?>[]) attrs.get("clients");
	if (clients == null || clients.length == 0) {
		scanner.addIncludeFilter(annotationTypeFilter);
		basePackages = getBasePackages(metadata);
	}
	else {
		final Set<String> clientClasses = new HashSet<>();
		basePackages = new HashSet<>();
		for (Class<?> clazz : clients) {
			basePackages.add(ClassUtils.getPackageName(clazz));
			clientClasses.add(clazz.getCanonicalName());
		}
		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
			@Override
			protected boolean match(ClassMetadata metadata) {
				String cleaned = metadata.getClassName().replaceAll("\\$", ".");
				return clientClasses.contains(cleaned);
			}
		};
		scanner.addIncludeFilter(
				new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
	}

	for (String basePackage : basePackages) {    // 第一层for循环就是解析不同basePackage下面的@FeignClients注解的声明,就是那些类下的方法
		Set<BeanDefinition> candidateComponents = scanner
				.findCandidateComponents(basePackage);   // 找到每一个basePackage下面的bean定义,因为不会同名,所有放到set里面
		for (BeanDefinition candidateComponent : candidateComponents) {  // 第二层for循环就是一个basePackage下的bean  
			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"));    // 核心方法1

				registerFeignClient(registry, annotationMetadata, attributes);  // 核心方法2
			}
		}
	}
}

两层for循环,第一层是遍历basePackages数组的各个元素,第二层是遍历basePackage的各种bean.

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);  // 通过builer的genericBeanDefinition生成了一个factorybean
				// 对实参 Map<String, Object> attributes 先校验,然后取出里面的东西
		validate(attributes);    //在获取属性之前先校验
		definition.addPropertyValue("url", getUrl(attributes));   // 第一,attributes中的url放到definition中
		definition.addPropertyValue("path", getPath(attributes));   // 第二,path放进去
		String name = getName(attributes);
		definition.addPropertyValue("name", name);   // 第三,name放进去
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);  // 第四,contextId放进去
		definition.addPropertyValue("type", className);    // 第五,type放进去
		definition.addPropertyValue("decode404", attributes.get("decode404"));  //第六,decode404放进去
		definition.addPropertyValue("fallback", attributes.get("fallback"));  //第七,fallback放进去
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));  //第八,fallbackFactory放进去
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);  // 第九,设置Autowired注解

		String alias = contextId + "FeignClient";
		//注意,definition是一个FeignClientFactoryBean,是一个factoryBean,这里用factorybean得到一个bean
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); //得到bean
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); //设置属性

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary"); // 实参attributes中取出参数

		beanDefinition.setPrimary(primary);// bean设置为实参中的参数

		String qualifier = getQualifier(attributes);// 实参attributes中继续取出参数
		if (StringUtils.hasText(qualifier)) {//如果有,设置为alias,
			alias = qualifier;
		}
        // 从beanDefinition得到holder,再从holder到spring容器中
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
				// 一切设置好之后,这里就是注册一个bean
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);  
	}

关于genericBeanDefinition()方法,

public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
    BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
    builder.beanDefinition.setBeanClass(beanClass);
    return builder;
}

FeignClientFactoryBean有什么特殊之处?
它是一个FactoryBean,和普通的bean不同,是用来生产bean的,里面有一个getObject方法

FeignClientFactoryBean类

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

/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
	FeignContext context = this.applicationContext.getBean(FeignContext.class);  //ioc容器中拿
	Feign.Builder builder = feign(context);// 调用feign方法

	if (!StringUtils.hasText(this.url)) {  // url没有内容,自己组装
		if (!this.name.startsWith("http")) {
			this.url = "http://" + this.name;
		}
		else {
			this.url = this.name;
		}
		this.url += cleanPath();
		return (T) loadBalance(builder, context,
				new HardCodedTarget<>(this.type, this.name, this.url));
	}
	if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
		this.url = "http://" + this.url;
	}
	String url = this.url + cleanPath();
	Client client = getOptional(context, Client.class);
	if (client != null) {
		if (client instanceof LoadBalancerFeignClient) {
			// not load balancing because we have a url,
			// but ribbon is on the classpath, so unwrap
			client = ((LoadBalancerFeignClient) client).getDelegate();
		}
		if (client instanceof FeignBlockingLoadBalancerClient) {
			// not load balancing because we have a url,
			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
			client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
		}
		builder.client(client);
	}
	Targeter targeter = get(context, Targeter.class);
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}

在这里插入图片描述
待续,未完,请看“OpenFeign,声明式的伪RPC调用(二)”。

四、尾声

OpenFeign,声明式的伪RPC调用,完成了。

天天打码,天天进步!!