Feign源码分析,面试四面一般会问什么问题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

private void registerDefaultConfiguration(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

Map<String, Object> defaultAttrs = metadata

.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

if (defaultAttrs != null && defaultAttrs.containsKey(“defaultConfiguration”)) {

String name;

if (metadata.hasEnclosingClass()) {

name = “default.” + metadata.getEnclosingClassName();

}

else {

name = “default.” + metadata.getClassName();

}

registerClientConfiguration(registry, name,

defaultAttrs.get(“defaultConfiguration”));

}

}

程序启动后通过包扫描,当类有@FeignClient注解,将注解的信息取出,连同类名一起取出,赋给BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到beanDefinition,最后beanDefinition式注入到ioc容器中,源码如下:

public void registerFeignClients(AnnotationMetadata metadata,

BeanDefinitionRegistry registry) {

ClassPathScanningCandidateComponentProvider scanner = getScanner();

scanner.setResourceLoader(this.resourceLoader);

Set 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 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) {

Set candidateComponents = scanner

.findCandidateComponents(basePackage);

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”);

Map<String, Object> attributes = annotationMetadata

.getAnnotationAttributes(

FeignClient.class.getCanonicalName());

String name = getClientName(attributes);

registerClientConfiguration(registry, name,

attributes.get(“configuration”));

registerFeignClient(registry, annotationMetadata, attributes);

}

}

}

}

private void registerFeignClient(BeanDefinitionRegistry registry,

AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {

String className = annotationMetadata.getClassName();

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);

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 = name + “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);

}

注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类,代码如下:

public T newInstance(Target target) {

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);

Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();

List defaultMethodHandlers = new LinkedList();

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)));

}

}

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);

}

return proxy;

}

在SynchronousMethodHandler类进行拦截处理,当被FeignClient的方法被拦截会根据参数生成RequestTemplate对象,该对象就是http请求的模板,代码如下:

@Override

public Object invoke(Object[] argv) throws Throwable {

RequestTemplate template = buildTemplateFromArgs.create(argv);

Retryer retryer = this.retryer.clone();

while (true) {

try {

return executeAndDecode(template);

} catch (RetryableException e) {

retryer.continueOrPropagate(e);

if (logLevel != Logger.Level.NONE) {

logger.logRetry(metadata.configKey(), logLevel);

}

continue;

}

}

}

其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后根据用client获取response。

Object executeAndDecode(RequestTemplate template) throws Throwable {

Request request = targetRequest(template);

…//省略代码

response = client.execute(request, options);

…//省略代码

}

Client组件


其中Client组件是一个非常重要的组件,Feign最终发送request请求以及接收response响应,都是由Client组件完成的,其中Client的实现类,只要有Client.Default,该类由HttpURLConnnection实现网络请求,另外还支持HttpClient、Okhttp.

首先来看以下在FeignRibbonClient的自动配置类,FeignRibbonClientAutoConfiguration ,主要在工程启动的时候注入一些bean,其代码如下:

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })

@Configuration

@AutoConfigureBefore(FeignAutoConfiguration.class)

public class FeignRibbonClientAutoConfiguration {

@Bean

@ConditionalOnMissingBean

public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

SpringClientFactory clientFactory) {

return new LoadBalancerFeignClient(new Client.Default(null, null),

cachingFactory, clientFactory);

}

}

在缺失配置feignClient的情况下,会自动注入new Client.Default(),跟踪Client.Default()源码,它使用的网络请求框架为HttpURLConnection,代码如下:

@Override

public Response execute(Request request, Options options) throws IOException {

HttpURLConnection connection = convertAndSend(request, options);

return convertResponse(connection).toBuilder().request(request).build();

}

怎么在feign中使用HttpClient,查看FeignRibbonClientAutoConfiguration的源码

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })

@Configuration

@AutoConfigureBefore(FeignAutoConfiguration.class)

public class FeignRibbonClientAutoConfiguration {

…//省略代码

@Configuration

@ConditionalOnClass(ApacheHttpClient.class)

@ConditionalOnProperty(value = “feign.httpclient.enabled”, matchIfMissing = true)

protected static class HttpClientFeignLoadBalancedConfiguration {

@Autowired(required = false)

private HttpClient httpClient;

@Bean

@ConditionalOnMissingBean(Client.class)

public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

SpringClientFactory clientFactory) {

ApacheHttpClient delegate;

if (this.httpClient != null) {

delegate = new ApacheHttpClient(this.httpClient);

}

else {

delegate = new ApacheHttpClient();

}

return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);

}

}

…//省略代码

}

从代码@ConditionalOnClass(ApacheHttpClient.class)注解可知道,只需要在pom文件加上HttpClient的classpath就行了,另外需要在配置文件上加上feign.httpclient.enabled为true,从 @ConditionalOnProperty注解可知,这个可以不写,在默认的情况下就为true.

在pom文件加上:

com.netflix.feign

feign-httpclient

RELEASE

同理,如果想要feign使用Okhttp,则只需要在pom文件上加上feign-okhttp的依赖:

com.netflix.feign

feign-okhttp

RELEASE

feign的负载均衡是怎么样实现的呢?


通过上述的FeignRibbonClientAutoConfiguration类配置Client的类型(httpurlconnection,okhttp和httpclient)时候,可知最终向容器注入的是LoadBalancerFeignClient,即负载均衡客户端。现在来看下LoadBalancerFeignClient的代码:

@Override

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 {

RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);

LoadBalancerCommand command = LoadBalancerCommand.builder()

.withLoadBalancerContext(this)

.withRetryHandler(handler)

.withLoadBalancerURI(request.getUri())

.build();

try {

return command.submit(

new ServerOperation() {

@Override

public Observable call(Server server) {

URI finalUri = reconstructURIWithServer(server, request.getUri());

S requestForServer = (S) request.replaceUri(finalUri);

try {

return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-SSO5asYh-1713600252556)]

Mybatis面试专题

[外链图片转存中…(img-3TodFk59-1713600252556)]

MySQL面试专题

[外链图片转存中…(img-RK7LwAdF-1713600252557)]

并发编程面试专题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-V0CCxwsG-1713600252557)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 53
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值