前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Feign源码解析

Spring Cloud Feign源码解析

作者头像
算法之名
发布2019-11-04 11:24:42
5010
发布2019-11-04 11:24:42
举报
文章被收录于专栏:算法之名算法之名

我们知道要使用feign,需要在springboot启动类放入@EnableFeignClients开关来打开feign的使用。

代码语言:javascript
复制
@EnableFeignClients
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {

   public static void main(String[] args) {
      SpringApplication.run(GatewayApplication.class, args);
   }

}

然后会使用例如

代码语言:javascript
复制
@FeignClient("user-center")
public interface UserClient {
    @PostMapping("/users-anon/finduser")
    LoginAppUser findUserByName(@RequestParam("username") String username);
}

现在我们可以考虑的方向是打上了@FeignClient("user-center")标签后,UserClient接口是如何被实例化成对象的,然后是在Controller中调用UserClient对象是如何进行网络请求的。

代码语言:javascript
复制
@Slf4j
@RestController
public class TokenController {

    @Autowired
    private Oauth2Client oauth2Client;
    @Autowired
    private UserClient userClient;


    /**
     * 系统登陆<br>
     * 根据用户名登录<br>
     * 采用oauth2密码模式获取access_token和refresh_token
     *
     * @param username
     * @param password
     * @return
     */
    @SuppressWarnings("unchecked")
    @PostMapping("/sys/login")
    public Result<Map> login(@RequestParam String username,@RequestParam String password) {
        Map<String, String> parameters = new HashMap<>();
        parameters.put(OAuth2Utils.GRANT_TYPE, "password");
        parameters.put(OAuth2Utils.CLIENT_ID, "system");
        parameters.put("client_secret", "system");
        parameters.put(OAuth2Utils.SCOPE, "app");
//    parameters.put("username", username);
        // 为了支持多类型登录,这里在username后拼装上登录类型
        parameters.put("username", username + "|" + CredentialType.USERNAME.name());
        parameters.put("password", password);

        Map<String, Object> tokenInfo = oauth2Client.postAccessToken(parameters);
        AppUser user = userClient.findUserByName(username);
        tokenInfo.put("user",user);
        saveLoginLog(username, "用户名密码登陆");

        return Result.success(tokenInfo);
    }

在feign的框架源码中,有3个类是比较重要的,FeignClientFactoryBean,FeignContext,SynchronousMethodHandler.

我们先来看一下@EnableFeignClients的作用。

代码语言:javascript
复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)  //用于处理@FeignClient注解
public @interface EnableFeignClients {
   //以下这三个都是用于指定需要扫描的包
   String[] value() default {};
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
   //用于自定义feign client的自定义配置,可以配置Decoder(解码器),Encoder(编码器)和Contract等组件,
   //FeignClientsConfiguration是默认的配置类
   Class<?>[] defaultConfiguration() default {};
   //指定被@FeignClient修饰的类,如果不为空,那么路径自动检测机制会被关闭
   Class<?>[] clients() default {};
}

@Import注解有四个作用

  1. 声明一个bean
  2. 导入@Configuration注解的配置类
  3. 导入ImportSelector的实现类
  4. 导入ImportBeanDefinitionRegistrar的实现类

FeignClientsRegistrar是用来处理@FeignClient修饰的FeignClient接口类,将这些接口类的BeanDefinition注册到Spring容器中,这样就可以使用@Autowired等方式来自动装载这些@FeignClient接口类的Bean实例。

代码语言:javascript
复制
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
      ResourceLoaderAware, EnvironmentAware

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,该接口是Spring实现bean动态注入的。我们来看一下该接口的方法

代码语言:javascript
复制
public void registerBeanDefinitions(
      AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

在FeignClientsRegistrar中的实现

代码语言:javascript
复制
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   //从EnableFeignClients的属性值来构建Feign的自定义Configuration进行注册
   registerDefaultConfiguration(metadata, registry);
   //扫描package,注册被@FeignClient修饰的接口类的Bean信息
   registerFeignClients(metadata, registry);
}
代码语言:javascript
复制
private void registerDefaultConfiguration(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   //将@EnableFeignClients的所有属性值导入map中
   Map<String, Object> defaultAttrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
   //如果@EnableFeignClients配置了defaultConfiguration,则往下运行。如果没有,rantion
   //会使用默认的FeignConfiguration,一般我们这里不会设置这些配置,都是默认,但是如果设置了,
   //就会好比我们自己写了@Configuration的类一样的效果,这里是自动配置了
   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"));
   }
}
代码语言:javascript
复制
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   //获取一个FeignClientSpecification的建造器
   BeanDefinitionBuilder builder = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientSpecification.class);
   //传入构造参数
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   //将FeignClientSpecification当成内部类对象进行注册
   registry.registerBeanDefinition(
         name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}

以上这里可以方便我们学习怎么写一个标签达到自动配置效果。以下是扫描包,并把打了@FeignClient标签的接口给加载到Spring容器中。

代码语言:javascript
复制
public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   ClassPathScanningCandidateComponentProvider scanner = getScanner();
   scanner.setResourceLoader(this.resourceLoader);

   Set<String> 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<String> 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<BeanDefinition> 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);
         }
      }
   }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档