前言
本篇内容着重讲述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);
}
}
④断点调试
上面可以看到,这种调用方式比普通的方法多了两个注解,直接导致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) {
registerDefaultConfiguration(metadata, registry);
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));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
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"));
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, () -> {
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));
}
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)) {
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,
new HardCodedTarget<>(type, name, url));
}
.........
可以看到这个地方传入了一个HardCodedTarget对象,可以发现这个对象就是上面TestService的代理对象 然后看下loadBalance方法 这里可以看到有一个hystrixTarget,然后看下对应的target方法 因为我配置过fallbackFactory,所以会走到targetWithFallbackFactory方法中 继续看看build.target方法 再看下buid做了啥 这里最主要的是复写了create方法,返回了一个HystrixInvocationHandler实例,大家注意下这个类,下面会用到,然后我们回过头再看下build(fallbackFactory).newInstance(target);
public <T> T newInstance(Target<T> target) {
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)));
}
}
}
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对象 可以发现和我们最开始注入的对象是一样的结构,ok这里关于怎么注入的feign就基本介绍完了
小结
①使用EnableFeignClients注解导入FeignClientsRegistrar类,在注册bean的时候调用registerBeanDefinitions方法 ②registerBeanDefinitions方法寻找贴了FeignClient注解的类并解析,最后把复写了工厂类的getObject方法,并把FeignClientFactoryBean注册到bean工厂 ③属性注入的时候会从bean容器中获取对应的feign客户端,这时会调用factoryBean.getObject(); ④获取实例的过程中生成一个代理类(HystrixInvocationHandler)注入到最终使用的地方
调用原理解析
通过上面我们可以知道注入的代理类是HystrixInvocationHandler,那么当我们调用方法时,会调用HystrixInvocationHandler的invoke方法,如下 注意重点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);
}
}
这里是通过调用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) {
.....
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(Server server) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
主要是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请求这个服务,获取结果,完成一次远程调用
|