对于不熟悉的人来说,源代码一般是庞大而“杂乱”的,要阅读源代码需要先找到入口点。
而openfeign的入口点在哪呢?当在项目中使用openfeig的时候是通过@EnableFeignClients注解来开启Openfeign,所以这个入口点就是@EnableFeignClients注解:
可以看到其和普通的注解没啥大的不同,除了@Import(FeignClientsRegistrar.class)注解,很显然,@EnableFeignClients注解的核心便是@Import注解。当在某个spring配置类或豆子上标记@EnableFeignClients注解时,相当于通过@import注解同时注册了FeignClientsRegistrar这个豆子----实际上,FeignClientsRegistrar并不是豆子,因为其实现了ImportBeanDefinitionRegistrar这个接口,然后会在registerBeanDefinitions()方法中手动注册豆子来代替FeignClientsRegistrar。总之现在关注点来到了FeignClientsRegistrar。
可以看到, FeignClientsRegistrar类实现了3个接口第一个用来手动注册豆子的,后两个接口是典型的Aware系列接口,可以注入对应的豆子,但这里有个疑问是,FeignClientsRegistrar并不是豆子为何还能注入?而且经过我的试验,这里如果使用ApplicationContextAware接口便不能成功,有待后面研究。于是现在关注点来到了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法
通过方法名能够猜测这里是先注册默认配置,再注册FeignClients,先看看怎么注册默认配置的
这里遇到了AnnotationMetadata类,顾名思义,注解元数据,即被@import注解了的类。这里便是通过该类获取到了被@import注解的类的另外一个注解---@EnableFeignClients的属性(@import注解是放在@EnableFeignClients注解上,然后@EnableFeignClients注解再放到某个配置类上的,所以@import注解便间接地放到了该配置类上)。接下来实际上是设置要注册地豆子地名称,跳过,直接进入registerClientConfiguration方法
没啥说的,通过BeanDefinitionBuilder创建FeignClientSpecification的BeanDefinition,然后直接注册该豆子,名字为default.全类名.FeignClientSpecification,到这里注册默认配置类结束。回到
进入下一个方法
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); } } } }
这个方法很长,很显然从这里便进入了OpenFeign初始化时的核心代码,即为每个FeignClient注册豆子。要注册豆子就得先找到该类的包路径,于是
先获取@EnableFeignClients注解的所有属性,然后优先检验clients属性的值,若不为空,则只会扫描clients属性指定的类的包路径,而不会去扫描被@feignClient注解了的类了。也就是说若clients设置了feignclient会使@feignClient注解失效!!!
若clients属性的值为空,才会去根据指定的包路径扫描被@feignClient注解了的类。这里的包路径是一个set集合,包含了@EnableFeignClients注解的value、basePackages、basePackagesClasses属性的值,若都为空,则使用被@EnableFeignClients注解的类的同级路径。代码如下:
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) { Map<String, Object> attributes = importingClassMetadata .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); Set<String> basePackages = new HashSet<>(); for (String pkg : (String[]) attributes.get("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : (String[]) attributes.get("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add( ClassUtils.getPackageName(importingClassMetadata.getClassName())); } return basePackages; }
找出包路径后,便开始扫描,这里使用了一个双重循环来扫描每个包路径下的每个类,并进行了一些判断,最终只有被@FeignClient注解了的接口留下来,然后开始注册,要注册就得有名字,所以先决定名字
很显然,名字的优先级从高到低为contextId、value、name、serviceId,若都为空则抛出异常。
找到名字后终于开始注册了
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); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); 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 = contextId + "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); }
显然,注册的豆子并不是被@feignclient注解的类,而是FeignClientFactoryBean,这里涉及到了spring中的FactoryBean,顾名思义就是工厂豆子,这里是hi机上是注册了被@feignclient注解的类的工厂豆子,当要再使用该类的地方注入时,spring会通过对应的工厂豆子的getObject()方法得到要使用的对象。总之factorybean就是一个简单工厂,我们定义好该工厂,要注入时spring会自动从该工厂获取豆子。
这里进行了一些琐碎的操作过后终于结束了openfeign的初始化。
总结一下就是:
当spring初始化时,会经过@EnableFeignClients注解上的@Import(FeignClientsRegistrar.class)注解进入FeignClientsRegistrar类的registerBeanDefinitions方法,之后便会根据注解的属性值找到feign clients们,最后为其注册FeignClientFactoryBean工厂豆子。
接下来就是feignclients的依赖注入……