通过一个例子看基于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. 接口方法的调用
下一篇文章在细谈。