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{
//......
@Override
public 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 {
@Override
public 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 unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((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
@Component
public class MyProxyFactory {
@Resource
private RestTemplate restTemplate;
@Resource
private 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
@SpringBootApplication
public class SpringCloudOpenfeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudOpenfeignApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
调用
@RestController
public class MyFeignController {
@Resource
private 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;
}
}
结果: