Fegin的源码分析
@EnableFeignClients
FeignClientsRegistrar
可以看到FeignClientsRegistrar 实现了三个接口
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware
其中:实现 ImportBeanDefinitionRegistrar重写registerBeanDefinitions()方法重点关注
public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}
从FeignClientsRegistrar 类找到registerBeanDefinitions()方法
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware{//......@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);//注册配置registerFeignClients(metadata, registry);//注册FeignClients}//.....}
到最后可以看到注册了bean定义.其中 BeanDefinitionBuilder 通过 FeignClientFactoryBean 来获取的
FeignClientFactoryBean
找到 FeignClientFactoryBean 的getObject()方法
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {@Overridepublic Object getObject() throws Exception {return getTarget();}<T> T getTarget() {FeignContext context = this.applicationContext.getBean(FeignContext.class);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();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));}}
targeter类

接着找到下面,可知 使用了jdk代理生成代理类
总结:到这里就知道,Springboot先扫描将FeignClient的代理对象注到Spring容器.然后就是使用的时候调用代理对象进行调用
源码图解
手写Feign的调用逻辑
- 通过注解获取服务名,接口方法,参数等信息
- 通过服务名取注册中心的服务地址列表
- 通过http调用,即可完成
创建ClientDiscoveryFeignClient
这里没有新创建注解,简单使用feign注解模拟
package com.itmck.client;import com.itmck.dto.User;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.*;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/11/2 20:54* <p>* 注意事项:* 1:Feign 里面定义的接口,有多个@RequestParam,但只能有不超过一个@RequestBody* 2:@FeignClient指定configuration后FeignConfig不能添加@Configuration**/@FeignClient(value = "springcloud-discovery")public interface ClientDiscoveryFeignClient {@ResponseBody@GetMapping("/product/getProduct")String getProduct(@RequestParam("name") String name);}
创建代理MyProxyFactory
package com.itmck.proxy;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.client.RestTemplate;import javax.annotation.Resource;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.net.URI;import java.util.List;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/12/12 16:33** [模拟feign的调用]* 逻辑就是使用jdk生成代理对象,通过http调用远程服务.** 步骤:* 1)通过 DiscoveryClient discoveryClient 获取注册中心实例的信息,然后拿到url* 2)通过 RestTemplate restTemplate 模拟get请求调用步骤 1)拿到的url.****/@Slf4j@Componentpublic class MyProxyFactory {@Resourceprivate RestTemplate restTemplate;@Resourceprivate DiscoveryClient discoveryClient;public <T> Object getInstance(Class<T> clazz) {return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy, method, args1) -> restGetOps(method, args1));}public Object restGetOps(Method method, Object[] args) {String serviceName = null;String path = null;Class<?> declaringClass = method.getDeclaringClass();if (declaringClass.isAnnotationPresent(FeignClient.class)) {FeignClient feignClient = declaringClass.getAnnotation(FeignClient.class);serviceName = feignClient.value();}if (method.isAnnotationPresent(GetMapping.class)) {GetMapping getMapping = method.getAnnotation(GetMapping.class);String url = getMapping.value()[0];List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);if (instances != null) {ServiceInstance serviceInstance = instances.get(0);if (serviceInstance != null) {URI uri = serviceInstance.getUri();path = uri.toString();}}url = path + url + "?name=" + args[0];log.info("url:{}", url);return restTemplate.getForObject(url, String.class);}return null;}}
测试
启动类注入
package com.itmck;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;/*** 太阳当空照,花儿对我笑* <p>* Create by M ChangKe 2021/12/12 11:32**/@EnableFeignClients@EnableDiscoveryClient@SpringBootApplicationpublic class SpringCloudOpenfeignApplication {public static void main(String[] args) {SpringApplication.run(SpringCloudOpenfeignApplication.class, args);}@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}}
调用
@RestControllerpublic class MyFeignController {@Resourceprivate MyProxyFactory myProxyFactory;@GetMapping("/hello")public String getStr() {ClientDiscoveryFeignClient clientDiscoveryFeignClient = (ClientDiscoveryFeignClient) myProxyFactory.getInstance(ClientDiscoveryFeignClient.class);String result = clientDiscoveryFeignClient.getProduct("mck");System.out.println(result);return result;}}
结果:

