Fegin的源码分析

@EnableFeignClients

image.png
image.png

FeignClientsRegistrar

可以看到FeignClientsRegistrar 实现了三个接口

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware

其中:实现 ImportBeanDefinitionRegistrar重写registerBeanDefinitions()方法重点关注

  1. public interface ImportBeanDefinitionRegistrar {
  2. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
  3. BeanNameGenerator importBeanNameGenerator) {
  4. registerBeanDefinitions(importingClassMetadata, registry);
  5. }
  6. default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  7. }
  8. }

FeignClientsRegistrar 类找到registerBeanDefinitions()方法

  1. class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{
  2. //......
  3. @Override
  4. public void registerBeanDefinitions(AnnotationMetadata metadata,
  5. BeanDefinitionRegistry registry) {
  6. registerDefaultConfiguration(metadata, registry);//注册配置
  7. registerFeignClients(metadata, registry);//注册FeignClients
  8. }
  9. //.....
  10. }

到最后可以看到注册了bean定义.其中 BeanDefinitionBuilder 通过 FeignClientFactoryBean 来获取的
image.png

FeignClientFactoryBean

找到 FeignClientFactoryBean 的getObject()方法

  1. class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
  2. @Override
  3. public Object getObject() throws Exception {
  4. return getTarget();
  5. }
  6. <T> T getTarget() {
  7. FeignContext context = this.applicationContext.getBean(FeignContext.class);
  8. Feign.Builder builder = feign(context);
  9. if (!StringUtils.hasText(this.url)) {
  10. if (!this.name.startsWith("http")) {
  11. this.url = "http://" + this.name;
  12. }
  13. else {
  14. this.url = this.name;
  15. }
  16. this.url += cleanPath();
  17. return (T) loadBalance(builder, context,
  18. new HardCodedTarget<>(this.type, this.name, this.url));
  19. }
  20. if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
  21. this.url = "http://" + this.url;
  22. }
  23. String url = this.url + cleanPath();
  24. Client client = getOptional(context, Client.class);
  25. if (client != null) {
  26. if (client instanceof LoadBalancerFeignClient) {
  27. // not load balancing because we have a url,
  28. // but ribbon is on the classpath, so unwrap
  29. client = ((LoadBalancerFeignClient) client).getDelegate();
  30. }
  31. if (client instanceof FeignBlockingLoadBalancerClient) {
  32. // not load balancing because we have a url,
  33. // but Spring Cloud LoadBalancer is on the classpath, so unwrap
  34. client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
  35. }
  36. builder.client(client);
  37. }
  38. Targeter targeter = get(context, Targeter.class);
  39. return (T) targeter.target(this, builder, context,
  40. new HardCodedTarget<>(this.type, this.name, url));
  41. }
  42. }

targeter类
image.png

image.png
接着找到下面,可知 使用了jdk代理生成代理类
image.png

总结:到这里就知道,Springboot先扫描将FeignClient的代理对象注到Spring容器.然后就是使用的时候调用代理对象进行调用

源码图解

lALPJxf-v42HfYPNAzHNBCA_1056_817.png

手写Feign的调用逻辑

  • 通过注解获取服务名,接口方法,参数等信息
  • 通过服务名取注册中心的服务地址列表
  • 通过http调用,即可完成

image.png

创建ClientDiscoveryFeignClient

这里没有新创建注解,简单使用feign注解模拟

  1. package com.itmck.client;
  2. import com.itmck.dto.User;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.*;
  5. /**
  6. * 太阳当空照,花儿对我笑
  7. * <p>
  8. * Create by M ChangKe 2021/11/2 20:54
  9. * <p>
  10. * 注意事项:
  11. * 1:Feign 里面定义的接口,有多个@RequestParam,但只能有不超过一个@RequestBody
  12. * 2:@FeignClient指定configuration后FeignConfig不能添加@Configuration
  13. **/
  14. @FeignClient(value = "springcloud-discovery")
  15. public interface ClientDiscoveryFeignClient {
  16. @ResponseBody
  17. @GetMapping("/product/getProduct")
  18. String getProduct(@RequestParam("name") String name);
  19. }

创建代理MyProxyFactory

  1. package com.itmck.proxy;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.cloud.client.ServiceInstance;
  4. import org.springframework.cloud.client.discovery.DiscoveryClient;
  5. import org.springframework.cloud.openfeign.FeignClient;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.client.RestTemplate;
  9. import javax.annotation.Resource;
  10. import java.lang.reflect.Method;
  11. import java.lang.reflect.Proxy;
  12. import java.net.URI;
  13. import java.util.List;
  14. /**
  15. * 太阳当空照,花儿对我笑
  16. * <p>
  17. * Create by M ChangKe 2021/12/12 16:33
  18. *
  19. * [模拟feign的调用]
  20. * 逻辑就是使用jdk生成代理对象,通过http调用远程服务.
  21. *
  22. * 步骤:
  23. * 1)通过 DiscoveryClient discoveryClient 获取注册中心实例的信息,然后拿到url
  24. * 2)通过 RestTemplate restTemplate 模拟get请求调用步骤 1)拿到的url.
  25. *
  26. *
  27. **/
  28. @Slf4j
  29. @Component
  30. public class MyProxyFactory {
  31. @Resource
  32. private RestTemplate restTemplate;
  33. @Resource
  34. private DiscoveryClient discoveryClient;
  35. public <T> Object getInstance(Class<T> clazz) {
  36. return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy, method, args1) -> restGetOps(method, args1));
  37. }
  38. public Object restGetOps(Method method, Object[] args) {
  39. String serviceName = null;
  40. String path = null;
  41. Class<?> declaringClass = method.getDeclaringClass();
  42. if (declaringClass.isAnnotationPresent(FeignClient.class)) {
  43. FeignClient feignClient = declaringClass.getAnnotation(FeignClient.class);
  44. serviceName = feignClient.value();
  45. }
  46. if (method.isAnnotationPresent(GetMapping.class)) {
  47. GetMapping getMapping = method.getAnnotation(GetMapping.class);
  48. String url = getMapping.value()[0];
  49. List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
  50. if (instances != null) {
  51. ServiceInstance serviceInstance = instances.get(0);
  52. if (serviceInstance != null) {
  53. URI uri = serviceInstance.getUri();
  54. path = uri.toString();
  55. }
  56. }
  57. url = path + url + "?name=" + args[0];
  58. log.info("url:{}", url);
  59. return restTemplate.getForObject(url, String.class);
  60. }
  61. return null;
  62. }
  63. }

测试

启动类注入

  1. package com.itmck;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.web.client.RestTemplate;
  8. /**
  9. * 太阳当空照,花儿对我笑
  10. * <p>
  11. * Create by M ChangKe 2021/12/12 11:32
  12. **/
  13. @EnableFeignClients
  14. @EnableDiscoveryClient
  15. @SpringBootApplication
  16. public class SpringCloudOpenfeignApplication {
  17. public static void main(String[] args) {
  18. SpringApplication.run(SpringCloudOpenfeignApplication.class, args);
  19. }
  20. @Bean
  21. public RestTemplate restTemplate() {
  22. return new RestTemplate();
  23. }
  24. }

调用

  1. @RestController
  2. public class MyFeignController {
  3. @Resource
  4. private MyProxyFactory myProxyFactory;
  5. @GetMapping("/hello")
  6. public String getStr() {
  7. ClientDiscoveryFeignClient clientDiscoveryFeignClient = (ClientDiscoveryFeignClient) myProxyFactory.getInstance(ClientDiscoveryFeignClient.class);
  8. String result = clientDiscoveryFeignClient.getProduct("mck");
  9. System.out.println(result);
  10. return result;
  11. }
  12. }

结果:
image.png