Feign封装请求基本原理(启动和注入)

通过一个例子看基于Feign的http请求原理。对Feign的操作主要分为:项目启动时处理、IOC容器注入时处理;实际的调用处理。目录:
1、例子;
2、项目启动时:Feign引入;
3、Bean的注入:@Autowired等;
4、接口方法的调用;

1. 例子

例子是一个用Maven管理的Spring Boot项目。

pom.xml添加feign依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
	<version>2.2.2.RELEASE</version>
</dependency>

这里只引用一个依赖,它会自动依赖其他Feign相关项。

FeignClient调用接口,添加@FeignClient注解

package com.mingo.exp.feign.remote;

import com.mingo.exp.feign.TestFeignRequestInterceptor;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * test 1
 *
 * @author Doflamingo
 */
@FeignClient(name = "test1", url = "http://hq.sinajs.cn", configuration = TestFeignRequestInterceptor.class)
public interface FeignTest1Service {

    /**
     * 获取<>hq.sinajs.cn</>网站数据
     *
     * @param list
     * @return
     */
    @GetMapping("/")
    String getStockInfo(@RequestParam("list") String list);
}

这里还添加了一个Feign请求拦截器

package com.mingo.exp.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;

/**
 * Feign请求拦截
 *
 * @author Doflamingo
 */
@Configuration
public class TestFeignRequestInterceptor implements RequestInterceptor {

    /**
     * @param requestTemplate 请求模板
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 这里我未操作requestTemplate 只打印一行文本
        System.out.println("Feign请求拦截...");
    }
}

测试类

package com.mingo.exp.feign;

import com.mingo.exp.feign.remote.FeignTest1Service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class FeignRequestTest {

    @Autowired
    private FeignTest1Service feignTest1Service;

    @Test
    public void Test_1() {
        String result = feignTest1Service.getStockInfo("sz000063");
        System.out.println(result);
    }
}

在启动类上要添加@EnableFeignClients注解

package com.mingo.exp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @author Doflamingo
 */
@SpringBootApplication
@EnableFeignClients
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

运行结果

Feign请求拦截...
var hq_str_sz000063="中兴通讯,39.710,39.540,38.910,39.740,38.880,38.900,38.910,55869686,2183069899.570,516348,38.900,129234,38.890,575200,38.880,52700,38.870,66800,38.860,37900,38.910,127819,38.920,49100,38.930,24811,38.940,82444,38.950,2020-07-30,15:00:03,00";

 

2. 项目启动时:Feign引入

对Feign的处理主要通过两个注解:@EnableFeignClients 和 @FeignClient。配置类加上@EnableFeignClients注解,在项目启动时Spring IOC容器注册Feign相关Bean。最终目的就是生成@FeignClient注解接口的代理类对象。

先看@EnableFeignClients,如下图

这里通过@Import(FeignClientsRegistrar.class)对Feign进行相关初始化,实现了ImportBeanDefinitionRegistrar接口,在项目启动时Spring会调用registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry)方法。

项目启动时

FeignClientsRegistrar类相关代码如下


// ImportBeanDefinitionRegistrar接口方法,项目启动时会执行该方法
// 这里的AnnotationMetadata存放启动类(com.mingo.exp.DemoApplication)相关元数据信息
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	// Feign Client缺省配置
	registerDefaultConfiguration(metadata, registry);
	// 处理@FeignClient注解接口
	registerFeignClients(metadata, registry);
}

// 默认的Feign配置类
private void registerDefaultConfiguration(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	// 获取@EnableFeignClients注解的属性
	Map<String, Object> defaultAttrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

	if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
		// default.com.mingo.exp.DemoApplication.FeignClientSpecification
		String name;
		if (metadata.hasEnclosingClass()) {
			name = "default." + metadata.getEnclosingClassName();
		}
		else {
			name = "default." + metadata.getClassName();
		}
		// Class<?>[] defaultConfiguration() default {};
		registerClientConfiguration(registry, name,
				defaultAttrs.get("defaultConfiguration"));
	}
}

// 处理@FeignClient注解接口
public void registerFeignClients(AnnotationMetadata metadata,
		BeanDefinitionRegistry registry) {
	// 这里用于搜索被@FeignClient注解的接口
	ClassPathScanningCandidateComponentProvider scanner = getScanner();
	scanner.setResourceLoader(this.resourceLoader);

	Set<String> basePackages;

	Map<String, Object> attrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName());
	// @FeignClient注解的标记
	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
			FeignClient.class);
	// 判断@EnableFeignClients注解clients属性是否有值
	final Class<?>[] clients = attrs == null ? null
			: (Class<?>[]) attrs.get("clients");
	if (clients == null || clients.length == 0) {
		// clients属性没值时才在basePackages中扫描被@FeignClient注解的接口
		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) {
		// 找到被@FeignClient注解的类
		Set<BeanDefinition> candidateComponents = scanner
				.findCandidateComponents(basePackage);
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				// 本例是com.mingo.exp.feign.remote.FeignTest1Service接口元数据信息
				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());
				// 这里面会判断name或value是否存在
				String name = getClientName(attributes);
				// 本例是注册com.mingo.exp.feign.TestFeignRequestInterceptor
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));

				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
}

// 这里为每一个合法@FeignClient注解的接口生成一个FeignClientFactoryBean对象,注册到IOC容器
private void registerFeignClient(BeanDefinitionRegistry registry,
		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
	String className = annotationMetadata.getClassName();
	// FeignClientFactoryBean实现了FactoryBean接口,用于装饰和实例化Feign的客服端
	// 其getObject()可得到动态代理的客户端对象
	BeanDefinitionBuilder definition = BeanDefinitionBuilder
			.genericBeanDefinition(FeignClientFactoryBean.class);
	validate(attributes);
	definition.addPropertyValue("url", getUrl(attributes));
	definition.addPropertyValue("path", getPath(attributes));
	String name = getName(attributes);
	definition.addPropertyValue("name", name);
	String contextId = getContextId(attributes);
	definition.addPropertyValue("contextId", contextId);
	definition.addPropertyValue("type", className);
	definition.addPropertyValue("decode404", attributes.get("decode404"));
	definition.addPropertyValue("fallback", attributes.get("fallback"));
	definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

    // 本例是test1FeignClient
	String alias = contextId + "FeignClient";
	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

	boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null

	beanDefinition.setPrimary(primary);

	String qualifier = getQualifier(attributes);
	if (StringUtils.hasText(qualifier)) {
		alias = qualifier;
	}

	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
			new String[] { alias });
	// 注册
	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

// 用于注册Feign相关的ClientConfiguration,如拦截器等
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
		Object configuration) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder
			.genericBeanDefinition(FeignClientSpecification.class);
	// FeignClientSpecification(String name, Class<?>[] configuration)的构造器
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	// 注册bean
	registry.registerBeanDefinition(
			name + "." + FeignClientSpecification.class.getSimpleName(),
			builder.getBeanDefinition());
}

__ FeignClientFactoryBean类实现了FactoryBean接口,用于装饰和实例化Feign的客服端。 FactoryBean为IOC容器生成Bean更加灵活。FactoryBean#getObject()方法得到的bean即是用@Autowired等真正注入的Feign客服端代理对象。__

3. Bean的注入:@Autowired等

也就是执行处理这行代码时

FeignClientFactoryBean怎样生成代理对象

// 在使用@Autowired等注入bean时,容器会调用getObject()生成业务真实的bean

// FactoryBean#getObject()实现
@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() {
	// 进行默认或设置的FeignClientsConfiguration的encoder、decoder、retryer等进行处理
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	Feign.Builder builder = feign(context);

    // 本例url = http://hq.sinajs.cn 所以不走这一步
    // 即是单纯的http请求,不需要配合Spring Cloud
	if (!StringUtils.hasText(this.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;
	}

	// 可是设置@FeignClient的path属性,会在这里接在url后面
	String url = this.url + cleanPath();
	// 本例是null
	Client client = getOptional(context, Client.class);
	// 单纯的http请求,不需要配合Spring Cloud
	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);
	}
	// HystrixTargeter
	Targeter targeter = get(context, Targeter.class);
	// 生成代理对象bean。HardCodedTarget对象用于存type、url等信息
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}

上述代码的进入HystrixTargeter.target(...)

进入Feign.Builder.target()

进入ReflectiveFeign.newInstance()


// ReflectiveFeign构造器
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
      QueryMapEncoder queryMapEncoder) {
	this.targetToHandlersByName = targetToHandlersByName;
	this.factory = factory;
	this.queryMapEncoder = queryMapEncoder;
}

// 生成代理对象
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
	// targetToHandlersByName.apply(target) 会对@FeignClient注解接口方法生成对应的SynchronousMethodHandler
	Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
	// 这里面存的就是每一个远程调用方法的实际处理 SynchronousMethodHandler
	Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
	List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

	for (Method method : target.type().getMethods()) {
	  if (method.getDeclaringClass() == Object.class) {
	    continue;
	  } else if (Util.isDefault(method)) {
	    DefaultMethodHandler handler = new DefaultMethodHandler(method);
	    defaultMethodHandlers.add(handler);
	    methodToHandler.put(method, handler);
	  } else {
	  	// Methed对象为key
	    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
	  }
	}
	// 生成对@FeignClient注解接口方法的代理处理
	InvocationHandler handler = factory.create(target, methodToHandler);
	// 生成代理对象
	T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);

	for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
	  defaultMethodHandler.bindTo(proxy);
	}
	// 生成的代理对象即是IOC容器注入的bean
	return proxy;
}

// 类FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {

	private final Target target;
	private final Map<Method, MethodHandler> dispatch;

    // InvocationHandler handler = factory.create(target, methodToHandler);这行代码触发
    // dispatch的key即是方法名,MethodHandler即是实际的代理处理(SynchronousMethodHandler)
	FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
	  this.target = checkNotNull(target, "target");
	  this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
	}
    
    // 代理处理@FeignClient接口的方法
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	  // 继承Object的方法处理
	  if ("equals".equals(method.getName())) {
	    try {
	      Object otherHandler =
	          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
	      return equals(otherHandler);
	    } catch (IllegalArgumentException e) {
	      return false;
	    }
	  } else if ("hashCode".equals(method.getName())) {
	    return hashCode();
	  } else if ("toString".equals(method.getName())) {
	    return toString();
	  }

      // 找到对应的SynchronousMethodHandler 并调用
	  return dispatch.get(method).invoke(args);
	}

	// 省略了其他代码
}

@FeignClient注解接口方法生成对应的SynchronousMethodHandler

生成的代理对象

4. 接口方法的调用

下一篇文章在细谈。

posted @ 2020-07-31 00:29  别名  阅读(2531)  评论(0编辑  收藏  举报