首先看一下demo工具的整体结构,ServiceB和ServiceA都要注册到eureka中,而serviceA-api是一个通用的api模块,由于Feign具有继承的特性,所以把接口都抽取出来了。
具体的代码如下:
@RequestMapping("/user") public interface ServiceAInterface { @RequestMapping(value = "/sayHello/{id}", method = RequestMethod.GET) String sayHello(@PathVariable("id") Long id, @RequestParam("name") String name, @RequestParam("age") Integer age); @RequestMapping(value = "/", method = RequestMethod.POST) String createUser(@RequestBody User user); @RequestMapping(value = "/{id}", method = RequestMethod.PUT) String updateUser(@PathVariable("id") Long id, @RequestBody User user); @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) String deleteUser(@PathVariable("id") Long id); @RequestMapping(value = "/{id}", method = RequestMethod.GET) User getById(@PathVariable("id") Long id); }
而在ServiceAClient中,只要对这个接口进行继承,加上注解就可以了。
@FeignClient("ServiceA") public interface ServiceAClient extends ServiceAInterface { }
还要在启动上加上开启Feign客户端的注解@EnableFeignClients,就能起作用了。那我们就点进去这个注解看看。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] defaultConfiguration() default {}; Class<?>[] clients() default {}; }
注解上面会注入Feign客户端的注册器,那我们就点进去看看。
ImportBeanDefinitionRegistrar来实现Bean的动态注入。它是Spring中一个强大的扩展接口,可以动态的往容器中,注入Bean,基本可以确定,就是实现它,来往容器中注入Feign的客户端的。
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }
首先先看一下第一个方法,名字注册一些默认的配置信息,点进去看看。大概的意思就是获取到EnableFeignClients这个注解上的一些信息,如果信息不为空,且包含模型配置的leu,就进入下面的判断。此方法不是核心的方法,主要留意一下registerFeignClients方法。
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); 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")); } }
那就点到registerFeignClients方法看看。
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()); //设置注解类型的过滤器,看上去是为了获取标记FeignClient这个注解类 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); //由于@EnableFeignClients注解中来配置clients属性,一般不会填,所以可以clients默认一般都是空的 if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); //getBasePackages这里肯定获取@EnableFeignClients这个注解的包的路径 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) { //1.这步的话就是根据包路径,去找到这个包下面有哪些FeignClient注解的类 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); } } } }
下面这个方法至关重要,那我们就点进去看看
Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage);
下面就来到scanCandidateComponents方法中
private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { //此处就是包的路径加上/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; //根据上面得到处理的路径,去拿到包路径下面所有的class文件,转成一个个Resource Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); //遍历所有的资源 for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); //此处方法最为重要,由于前面创建的scanner。重写了isCandidateComponent方法,这里就是从所有的class中 // 获取所有有的Feign的客户端 if (isCandidateComponent(metadataReader)) { ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to read candidate component class: " + resource, ex); } } else { if (traceEnabled) { logger.trace("Ignored because not readable: " + resource); } } } } catch (IOException ex) { throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); } return candidates; }
下面就断点进去看一下那个方法,就是之前把过滤条件传进来了,此处就可以获得了。
拿到所有的Feign客户端就准备开始注册了