了解完spring.cloud.config.client模块的代码,现在来了解下spring.cloud.config.monitor模块的代码。通过spring.factories启动文件可用看到只配置了一个自动配置类EnvironmentMonitorAutoConfiguration,我我们来看下这个类。
进入到EnvironmentMonitorAutoConfiguration类,通过头部的注解可以了解到它是一个配置类,并且只能运行在WebApplication环境,然后通过注解@Import引入了FileMonitorConfiguration类。
进入到FileMonitorConfiguration类,可以看到它实现了SmartLifecycle和ResourceLoaderAware接口,从这2个接口中可以看到一个是用于生命周期的管理,一个是用于资源文件的加载。通过FileMonitorConfiguration这个类的注解可以了解到它定义了一个定时任务。
首先看看FileMonitorConfiguration类实现的SmartLifecycle接口的start()方法
public synchronized void start() { if (!this.running) { this.directory = getFileRepo(); if (this.directory != null && !this.directory.isEmpty()) { log.info("Monitoring for local config changes: " + this.directory); try { this.watcher = FileSystems.getDefault().newWatchService(); for (Path path : this.directory) { walkDirectory(path); } } catch (IOException e) { } } else { log.info("Not monitoring for local config changes"); } this.running = true; } }
在初始化的时候字段running状态为false的时候通过getFileRepo()方法获取Set集合的文件路径
private SetgetFileRepo() { if (this.scmRepository != null) { try { Resource resource = this.resourceLoader.getResource(this.scmRepository.getUri()); if (resource instanceof FileSystemResource) { return Collections.singleton(Paths.get(resource.getURI())); } } catch (IOException e) { log.error("Cannot resolve URI for path: " + this.scmRepository.getUri()); } } if (this.nativeEnvironmentRepository != null) { Set paths = new LinkedHashSet<>(); for (String path : this.nativeEnvironmentRepository.getSearchLocations()) { Resource resource = this.resourceLoader.getResource(path); if (resource.exists()) { try { paths.add(Paths.get(resource.getURI())); } catch (Exception e) { log.error("Cannot resolve URI for path: " + path); } } } return paths; } return null; }
在getFileRepo()方法中,它是通过ResourceLoader去加载配置的服务地址,返回获取的Resource信息,如果本地环境nativeEnvironmentRepository不为空,则去本地环境加载路径值。获取到path路径的Set集合后通过 FileSystems.getDefault().newWatchService()定义一个WatchService的监控类,然后循环path路径的Set集合,通过walkDirectory()方法来添加对文件的监控信息,git的文件不需要。
在看EnvironmentMonitorAutoConfiguration类,它定义了一个PropertyPathEndpoint的bean,这个bean引入了Bus模块的相关配置,因为这个类要配合bus模块来应用,我们进入到PropertyPathEndpoint类,可以看到它是一个RestController的API类,可以通过spring.cloud.config.monitor.endpoint.path自定义访问路径,然后加上monitor路径进行访问,同时它实现了ApplicationEventPublisherAware接口用于事件发布,我们看下它的2个path路径,都是通过POST方式访问的,但是notifyByForm()方法定义了consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE的访问条件,最终也是调用notifyByPath()方法,我们看下该方法
@RequestMapping(method = RequestMethod.POST) public SetnotifyByPath(@RequestHeader HttpHeaders headers, @RequestBody Map request) { PropertyPathNotification notification = this.extractor.extract(headers, request); if (notification != null) { Set services = new LinkedHashSet<>(); for (String path : notification.getPaths()) { services.addAll(guessServiceName(path)); } if (this.applicationEventPublisher != null) { for (String service : services) { log.info("Refresh for: " + service); this.applicationEventPublisher .publishEvent(new RefreshRemoteApplicationEvent(this, this.busId, service)); } return services; } } return Collections.emptySet(); }
在notifyByPath()方法中,通过PropertyPathNotificationExtractor类的具体实现类去实现extract()方法,我们主要看下它的实现类BasePropertyPathNotificationExtractor类,这个类主要是用于git相关的解析,我们看下extract()方法
public PropertyPathNotification extract(MultiValueMapheaders, Map request) { if (requestBelongsToGitRepoManager(headers)) { if (request.get("commits") instanceof Collection) { Set paths = new HashSet<>(); @SuppressWarnings("unchecked") Collection
在extract()方法中,首先是通过requestBelongsToGitRepoManager()方法判断是否包含特定的推送信息,这里我们以GithubPropertyPathNotificationExtractor为例
protected boolean requestBelongsToGitRepoManager(MultiValueMapheaders) { return "push".equals(headers.getFirst("X-Github-Event")); }
通过requestBelongsToGitRepoManager()方法判断头部中的X-Github-Event是否是push事件,然后获取request集合中的commits信息,将获取到的文件改变的推送路径加载到paths集合中,返回PropertyPathNotification对象。
获取到PropertyPathNotification对象后,在PropertyPathEndpoint类的notifyByPath()方法中定义了一个services的LinkedHashSet集合,该services用来添加在PropertyPathNotification对象中获取到的path路径,我们看下guessServiceName()方法
private SetguessServiceName(String path) { Set services = new LinkedHashSet<>(); if (path != null) { String stem = StringUtils .stripFilenameExtension(StringUtils.getFilename(StringUtils.cleanPath(path))); // TODO: correlate with service registry int index = stem.indexOf("-"); while (index >= 0) { String name = stem.substring(0, index); String profile = stem.substring(index + 1); if ("application".equals(name)) { services.add("*:" + profile); } else if (!name.startsWith("application")) { services.add(name + ":" + profile); } index = stem.indexOf("-", index + 1); } String name = stem; if ("application".equals(name)) { services.add("*"); } else if (!name.startsWith("application")) { services.add(name); } } return services; }
在guessServiceName()方法中,首先对路径进行了消除获取到了字符串为stem的文件后缀的名字,然后通过一系列的判断对文件进行加*操作,用于事件发布的需要。退出guessServiceName()方法后,在PropertyPathEndpoint类的notifyByPath()方法中对services进行了事件的发布通过RefreshRemoteApplicationEvent事件。
介绍完PropertyPathEndpoint类,我们在回头看看EnvironmentMonitorAutoConfiguration类中定义的static类PropertyPathNotificationExtractorConfiguration,在该类中定义了一系列的PropertyPathNotificationExtractor接口实现类,在这些类上面都定义了prefix的配置路径,默认上是都加载,具体可以参考前面提到的GithubPropertyPathNotificationExtractor类,这里不在一一说明。