本文主要内容:
- 简单了解原生Feign、Spring Cloud Open Fegin是什么
一、Spring Cloud Open Fegin介绍
1.1、Feign基础介绍
Feign:是申明式Web服务客户端,基于JAX-RS(javaRest规范)实现的
- 申明式:通过接口声明、Annotation驱动
- Web服务:HTTP通讯协议
- 客户端:用户服务调用存根
Feign可以将一个Service声明为可以被HTTP方式调用,底层基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类上。
1.2、Spring Cloud Open Fegin基础介绍
Spring Cloud对原生Feign进行封装,使其支持SpringWebMvc,也就是Spring Cloud Open Feign
| REST框架 | 使用场景 | 请求映射注解 | 请求参数注解 |
|---|---|---|---|
| JAX-RS | 客户端、服务端声明 | @Path | @*Param |
| Feign | 客户端声明 | @RequestLine | @Param |
| Spring Web MVC | 服务端声明 | @ReqeustMapping | @RequestParam |
| SpringCloudOpenFeign | 客户端声明 | @ReqeustMapping | @RequestParam |
Spring Cloud Open Feign利用Feign的高扩展性,以及使用标准Spring Web MVC来声明客户端 Java 接口:
-
Feign
-
注解扩展性
-
HTTP 请求处理
-
REST 请求元信息解析
-
-
Spring Cloud Open Feign
- 提供 Spring Web MVC 注解处理
- 提供 Feign 自动装配
1.3、Spring Cloud Open Fegin的使用
1.3.1、引入Spring Cloud Open Fegin对应jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.3.2、启用Spring Cloud Open Fegin功能
启用Spring Cloud Open Fegin功能: 在启动类上添加注解@EnableFeignClients注解,并定义要扫描的包名
@EnableFeignClients(basePackages = "com.springcloud.feginclients")
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
1.3.1、声明一个FeignClient
准备一个接口:UserService.class
public interface UserService {
@GetMapping("/getUsers")
String getUsers();
@PostMapping("/addUsers")
int addUser(User user);
}
在定义的basePackages中创建对应的FeignClient,例如:UserServiceFeignClient .calss
- value: 需要和注册注册中心上的服务名称一致
- fallback: 请求失败时,会调用fallback指定类中的同名方法
@FeignClient(value = "user-service",
fallback = UserServiceFeignClient.UserServiceFeignClientFallback.class)
public interface UserServiceFeignClient extends UserService{
@Component
class UserServiceFeignClientFallback implements UserServiceFeignClient{
@Override
public String getUsers() {
return "查询用户列表失败,请重试";
}
@Override
public int addUser(User user) {
System.out.println("新增用户失败,请重试");
return -1;
}
}
}
1.3.4、通过接口方式调用FeignClient
通过@Autowired自动注入UserService,SpringCloud会为UserService创建一个动态代理存到IOC容器中
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/getUsers")
public List<User> getUsers(){
userService.getUsers();
}
@PostMapping("/addUsers")
public int addUser(User user){
userService.addUsers(user);
}
}
通过使用Spring Cloud Open Fegin我们可以想调用本地方法一样,去请求远程服务接口。下面我们就来分析一下它的原理
二、Spring Cloud Open Fegin原理分析
Spring Cloud Open Fegin的核心原理,可以简单总结为以下意思:Spring Cloud Open Fegin会为标注了@FeignClient注解的接口创建动态代理,然后通过这个代理发送HTTP请求到我们指定分服务端
其中的关键点有两个:
- Spring Cloud Open Fegin时如何加载并创建这个代理对象的?(2.1章分析说明)
- 代理对象如何完成远程服务调用?(2.2章分析说明)
2.1、为@FeignClient注册动态代理的流程图
Spring Cloud Open Fegin对于@FeignClient注册动态代理的流程大体可以分为以下三步:

2.1.1、步骤一:FeignClientsRegistrar注册FeignClientFactoryBean
(1)通过@EnableFeignClients启用功能
我在介绍SpringBean装载过程时,介绍过Spring的动态装配Bean:通过@Enable模式+@import方式,可导入三类Class文件
- 添加了@Configuration注解的配置类
- ImportSelector接口的实现类
- ImportBeanDefinitionRegistrar接口的实现类
@EnableFeignClients:通过导入ImportBeanDefinitionRegistrar接口实现类的方式,实现Bean的动态注入,源代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
增加@EnableFeignClients注解后,Spring会调用FeignClientsRegistrar.registerBeanDefinitions()方法将每一个@FeignClient注册到IOC容器中
FeignClientsRegistrar.registerBeanDefinitions()方法,源码如下:
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 如果EnableFeignClients配置了defaultConfiguration
// 注册一个FeignClientSpecification到IOC中,用于扫描自定义配置类
registerDefaultConfiguration(metadata, registry);
// 注册FeignClients:会每一个@FeignClient创建代理
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration方法:注册@EnableFeignClients中定义的defaultConfiguration默认配置(不做分析)
registerFeignClients()方法:会每一个@FeignClient创建代理(下面信息此方法源码)
(2)注册FeignClientFactoryBean到容器中
registerFeignClients.registerFeignClients()方法:会为每一个@FeignClient创建代理,它的核心功能如下:
定义一个scanner扫描器,并指定扫描路径、筛选条件:- 扫描路径:根据@EnableFeignClients中配置的参数,获取要扫描的包路径basePackages
- 设置一个AnnotationTypeFilter过滤器:根据@EnableFeignClients中配置的参数,指定要过滤的文件(添加@FeignClient注解的class文件,或者指定名称的class文件)
扫描指定basePackages包下,添加@FeignClient注解的class文件:数据会存储到一个Set列表钟,一个class文件对应一个BeanDefinition读取每一个BeanDefinition中@FeignClient注解中的属性根据@FeignClient注解中的属性,再生成一个FeignClientFactoryBean的BeanDefinition(PS:需要注意,这个BeanDefinition对应的是一个FeignClientFactoryBean(工厂Bean)。在Spring中,工厂Bean是用来创建复杂Bean对象的)注册BeanDefinition到容器中(FeignClientFactoryBean的BeanDefinition)
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 定义scanner扫描器,
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 获取EnableFeignClients中定义的basePackages
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 设置只过滤@FeignClient注解
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");
// EnableFeignClients没有定义clients属性时
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
// 根据参数获取要扫描的basePackages
basePackages = getBasePackages(metadata);
}
// EnableFeignClients定义了clients属性:则扫描指定的class文件
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)));
}
// 循环扫描basePackages
for (String basePackage : basePackages) {
// 扫描basePackage,获取对应class文件对应的BeanDefinition
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
// 循环注册BeanDefinition,逐一注册IOC容器中
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");
// 获取@FeignClient中配置参数
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 对应到IOC中的Bean Name
String name = getClientName(attributes);
// 为@FeignClient的configuration属性,注册一个FeignClientSpecification
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册FeignClient到IOC中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
/*
* 注册FeignClient到IOC中
*
*/
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 获得注解中的classname
String className = annotationMetadata.getClassName();
// 初始化BeanDefinitionBuilder:注意这是一个FeignClientFactoryBean工厂Bean
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);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 注册FeignClientFactoryBean的BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
2.1.2、步骤二:通过FeignClientFactoryBean获取动态代理对象
上面@EnableFeignClients会为每一个注解了@FeignClient的类,注册一个FeignClientFactoryBean到IOC容器中,当我们获取Bean时,就会调用这个FeignClientFactoryBean.getBean()方法。

FeignClientFactoryBean.getObject()内部会调用自身的getTarget()方法,我们直接看getTarget()方法的源码:
<T> T getTarget() {
// 1、获取FeignContext上下文
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 2、从FeignContext中,根据contextId获取ApplicationContext,然后获取Feign.Builder
// Feign.Builder 用户创建代理对象
Feign.Builder builder = feign(context);
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;
}
String url = this.url + cleanPath();
// 3、从FeignContext中,根据contextId获取ApplicationContext,然后获取Client
// Client是一个LoadBalancerFeignClient,用于实现客户端负载
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
// 4、从FeignContext中,根据contextId获取ApplicationContext,然后获取Targeter
// Targeter默认是DefaultTargeter
Targeter targeter = get(context, Targeter.class);
// 5、创建并返回代理对象
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
在上边代码中添加了5处注释,就不逐行对代码进行分析了,在此主要说一下每个注释方法实现的核心思想,消息源码清自行观看
2.1.2.1:注释1核心:FeignContext context
FeignContext继承了NamedContextFactory(在之前Ribbon的分析中LoadBalancerClientFactory也是继承NamedContextFactory,两者实现思路一致),NamedContextFactory内部有一个Map<String,
AnnotationConfigApplicationContext>,根据@FeignClient定义contextId(或者serviceid)不同,创建不同的AnnotationConfigApplicationContext进行隔离。- 通过FeignContext获取Bean时,
会在FeignContext的Map<String, AnnotationConfigApplicationContext>中,通过contextId获取对应的AnnotationConfigApplicationContext,然后在AnnotationConfigApplicationContext中通过类型获取对应的Bean

2.1.2.2:注释2核心: Feign.Builder
通过(1)中的FeignContext获取Feign.Builder,同时会将注解中配置的参数,设置到Feign.Builder中,后续用于创建Proxy代理对象
2.1.2.3:注释3核心: Client
借用(1)中的FeignContext获取Client,这个Client实质是一个LoadBalancerFeignClient,用于客户端针对contextId(或者serviceid)做负载均衡。
2.1.2.4:注释4核心:Targeter
Targeter:可以理解为获取目标代理对象,内部有时会对Feign.Builder参数进行优化,然后通过Feign.Builder创建Proxy代理对象
借用(1)中的FeignContext获取Targeter,默认是DefaultTargeter,内部会直接调用Feign.Builder创建Proxy代理对象。(当引入Hystrix时,Targeter会是HystrixTargeter,内部会针Hystrix对Feign.Builder修改设置,然后再调用Feign.Builder创建Proxy代理对象)
2.1.2.5:注释5核心:创建Proxy
此处传入了一个HardCodedTarget(Class<T> type, String name, String url)
FeignClientFactoryBean.getTarget根据以下调用链路,最终进入到ReflectiveFeign.newInstance()方法,具体创建代理对象

2.1.3、步骤三:ReflectiveFeign创建具体的动态代理对象
在ReflectiveFeign.newInstance()内部,可以分为4个部分,它们的主要作用如下:
- targetToHandlersByName.apply(target)方法: 基于SpringMVC,解析@FeginClient注解的类,并返回一个nameToHandler,其中的
MethodHandler是一个SynchronousMethodHandler(同步方法处理器) - 循环目标类中的Method,建立Method和MethodHandler的映射关系
- 为Method创建一个FeignInvocationHandler,并保存它们的映射关系到Map<Method, MethodHandler> methodToHandler。(PS:注意这个methodToHandler在后续方法调用中会用到)
- 通过JDK的Proxy创建并返回一个proxy代理对象,此proxy会调用FeignInvocationHandler.invoke()方法
源码分析:
public <T> T newInstance(Target<T> target) {
// 1、默认基于SpringMVC注解方式,解析@FeginClient注解的类
// nameToHandler:根据@RequestMapping设置的key,存储对应的MethodHandler的映射关系,MethodHandler是一个SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// 2、methodToHandler:基于method,存储对应的MethodHandler的映射关系
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 {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 3、此处创建是一个FeignInvocationHandler,(Proxy内部会调用InvocationHandler.invoke()方法)
InvocationHandler handler = factory.create(target, methodToHandler);
// 4、通过JDK的Proxy创建代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
上面是为@FeginClient创建动态代理的过程,下面开始力分析动态代理请求远程服务的调用过程
2.2、@FeignClient动态代理请求远程服务的调用过程
在上面分析中,我们知道Fegin通过JDK的Proxy创建动态代理,当我们调用这个proxy时,它的调用顺序是:
proxy…invoke()
–>FeignInvocationHandler.invoke()
–>SynchronousMethodHandler.invoke()
–>LoadBalancerFeignClient.execute(request, options)
2.2.1、FeignInvocationHandler.invoke()
这里的dispatch就是上面的2.1.3中的methodToHandler,存储了<method,MethodHandler>的映射关系:我们通过method可以获取一个对应的MethodHandler,然后进入MethodHandler.invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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();
}
// dispatch就是上面的methodToHandler:通过method获取一个MethodHandler(即SynchronousMethodHandler)
return dispatch.get(method).invoke(args);
}
2.2.2、SynchronousMethodHandler.invoke()
一般MethodHandler都是SynchronousMethodHandler,即:同步方法处理
此方法内部调用过程:
- 将请求参数,封装为一个RequestTemplate
- 调用LoadBalancerFeignClient.execute发送Http请求
- 处理并返回Response数据
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");
return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
if (cause != null)
throw cause;
throw e;
}
}
2.2.3、LoadBalancerFeignClient.execute(request, options)
方法内部基于Ribbon实现负载均衡调用,实现原理参考之前写的Ribbon原理分析
public Response execute(Request request, Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = this.getClientConfig(options, clientName);
return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
} catch (ClientException var8) {
IOException io = this.findIOException(var8);
if (io != null) {
throw io;
} else {
throw new RuntimeException(var8);
}
}
}
本文详细介绍了Spring Cloud OpenFeign,包括其基础概念、使用步骤以及核心原理分析。Feign是声明式Web服务客户端,而Spring Cloud OpenFeign在Feign基础上提供了Spring Web MVC注解处理和自动装配。文章讲解了从引入jar包、启用功能、声明FeignClient到调用远程服务的全过程,并深入剖析了动态代理的创建流程,包括FeignClientFactoryBean的注册、Feign.Builder的使用、Client和Targeter的角色,以及ReflectiveFeign如何创建代理对象。最后,文章阐述了动态代理请求远程服务的调用过程,涉及FeignInvocationHandler、SynchronousMethodHandler和LoadBalancerFeignClient的执行流程。

1185

被折叠的 条评论
为什么被折叠?



