spring cloud Config原始碼解析(三)
前面說完我們如何從github上面去取資料,這裡說說server端剩餘的類。ConfigServerEncryptionConfiguration類。從類的名字我們可以看出主要是加解密相關的配置類,進入類中可以看到定義了EncryptionController encryptionController()這個的bean,直接進入到EncryptionController類,controller類的訪問路徑可以通過spring.cloud.config.server.prefix屬性進行配置,在類中我們可以看到一系列的加解密相關操作的端點,這裡就不在具體介紹,可以參考程式碼檢視。需要注意的是
在看下EncryptionAutoConfiguration類,進入該類,我們可以瞭解到這裡定義了2個靜態內部類和2個一般內部類,分別是EncryptorConfiguration、KeyStoreConfiguration
在EncryptorConfiguration類中定義了在prefix是spring.cloud.config.server.encrypt.enabled
為true的條件下才能有效注入加解密的程式碼;KeyStoreConfiguration這個類主要是用於對稱加密的儲存;SingleTextEncryptorConfiguration這個類有效的情況是存在TextEncryptor的bean和不存在TextEncryptorLocator的bean時,這個時候才會初始化裡面的
說完server端的一些內部使用,但是我們外部如何來呼叫,接下來我們看看EnvironmentController和ResourceController這2個controller類的使用。
首先說說ResourceController類,進入到這個controller可以看到它是一個restful controller類,入口地址可以通過prefix為spring.cloud.config.server.prefix來定義總的入口,類中定義了3個RequestMapping
1.@RequestMapping("/{name}/{profile}/{label}/**")
@RequestMapping("/{name}/{profile}/{label}/**") public String retrieve(@PathVariable String name, @PathVariable String profile, @PathVariable String label, HttpServletRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { String path = getFilePath(request, name, profile, label); return retrieve(name, profile, label, path, resolvePlaceholders); }
從路徑上看它必須匹配有3個及以上的引數,返回型別是String字串,在getFilePath()方法裡面可以看到一系列的替換操作,這裡不貼出程式碼了,進入到retrieve()方法
synchronized String retrieve(String name, String profile, String label, String path, boolean resolvePlaceholders) throws IOException { if (name != null && name.contains("(_)")) { // "(_)" is uncommon in a git repo name, but "/" cannot be matched // by Spring MVC name = name.replace("(_)", "/"); } if (label != null && label.contains("(_)")) { // "(_)" is uncommon in a git branch name, but "/" cannot be matched // by Spring MVC label = label.replace("(_)", "/"); } // ensure InputStream will be closed to prevent file locks on Windows try (InputStream is = this.resourceRepository.findOne(name, profile, label, path) .getInputStream()) { String text = StreamUtils.copyToString(is, Charset.forName("UTF-8")); if (resolvePlaceholders) { Environment environment = this.environmentRepository.findOne(name, profile, label); text = resolvePlaceholders(prepareEnvironment(environment), text); } return text; } }
該方法是一個synchronized方法,首先做的是一些替換操作,然後進入到ResourceRepository介面的實現類GenericResourceRepository中的findOne()方法
public synchronized Resource findOne(String application, String profile, String label, String path) { String[] locations = this.service.getLocations(application, profile, label).getLocations(); try { for (int i = locations.length; i-- > 0;) { String location = locations[i]; for (String local : getProfilePaths(profile, path)) { Resource file = this.resourceLoader.getResource(location) .createRelative(local); if (file.exists() && file.isReadable()) { return file; } } } } catch (IOException e) { throw new NoSuchResourceException( "Error : " + path + ". (" + e.getMessage() + ")"); } throw new NoSuchResourceException("Not found: " + path); }
private Collection<String> getProfilePaths(String profiles, String path) { Set<String> paths = new LinkedHashSet<>(); for (String profile : StringUtils.commaDelimitedListToSet(profiles)) { if (!StringUtils.hasText(profile) || "default".equals(profile)) { paths.add(path); } else { String ext = StringUtils.getFilenameExtension(path); String file = path; if (ext != null) { ext = "." + ext; file = StringUtils.stripFilenameExtension(path); } else { ext = ""; } paths.add(file + "-" + profile + ext); } } paths.add(path); return paths; }
從實現上可以發現它也是一個synchronized方法,它首先會根據初始化的配置檔案去SearchPathLocator介面的實現類去陣列locations,它主要就是從配置來源做一些獲取配置檔案到本地的操作,可以通過前面介紹的git相關類參考,然後根據路徑返回配置檔案資訊,返回到retrieve()方法中後,去除一些不必要的資訊以及清除佔位符之類的後返回String格式的text。
2.@RequestMapping(value = "/{name}/{profile}/**", params = "useDefaultLabel")
@RequestMapping(value = "/{name}/{profile}/**", params = "useDefaultLabel") public String retrieve(@PathVariable String name, @PathVariable String profile, HttpServletRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { String path = getFilePath(request, name, profile, null); return retrieve(name, profile, null, path, resolvePlaceholders); }
這個請求基本和上面的請求差不多,範圍稍微大一些,但是必須包含特定引數useDefaultLabel,其他處理過程一樣。
3.@RequestMapping(value = "/{name}/{profile}/{label}/**", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@RequestMapping(value = "/{name}/{profile}/{label}/**", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public synchronized byte[] binary(@PathVariable String name, @PathVariable String profile, @PathVariable String label, HttpServletRequest request) throws IOException { String path = getFilePath(request, name, profile, label); return binary(name, profile, label, path); }
這個請求類的路徑也是和前面的差不多,但是必須包含返回的型別produces引數為MediaType.APPLICATION_OCTET_STREAM_VALUE的資料,在binary()方法中,其基本方法與前面的mapping路徑一樣,唯一不同就是把獲取到的檔案資訊直接轉為byte[]陣列。
我們接下來看看EnvironmentController類,進入到這個controller可以看到它是一個restful controller類,入口地址可以通過prefix為spring.cloud.config.server.prefix來定義總的入口,類中定義了8個RequestMapping。
1.@RequestMapping("/{name}/{profiles:.*[^-].*}")
@RequestMapping("/{name}/{profiles:.*[^-].*}") public Environment defaultLabel(@PathVariable String name, @PathVariable String profiles) { return labelled(name, profiles, null); }
通過mapping可以看到它的請求路徑是不含label且不能帶有-的路徑,而labelled()方法在下面的mapping中介紹,返回型別是一個Environment物件。
2.@RequestMapping("/{name}/{profiles}/{label:.*}")
@RequestMapping("/{name}/{profiles}/{label:.*}") public Environment labelled(@PathVariable String name, @PathVariable String profiles, @PathVariable String label) { if (name != null && name.contains("(_)")) { // "(_)" is uncommon in a git repo name, but "/" cannot be matched // by Spring MVC name = name.replace("(_)", "/"); } if (label != null && label.contains("(_)")) { // "(_)" is uncommon in a git branch name, but "/" cannot be matched // by Spring MVC label = label.replace("(_)", "/"); } Environment environment = this.repository.findOne(name, profiles, label); if(!acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())){ throw new EnvironmentNotFoundException("Profile Not found"); } return environment; }
這個路徑將接收全部型別的字尾包含路徑為name,profiles和label的路徑引數,方法中首先做了一些替換操作,然後通過呼叫具體的EnvironmentRepository介面的實現類方法findOne()去查詢配置檔案,具體可以參考前面提到的MultipleJGitEnvironmentRepository的實現,獲取到Environment後就返回。在client端,我們就會呼叫這個介面方法進行遠端配置資料的更新操作。
3.@RequestMapping("/{name}-{profiles}.properties")
@RequestMapping("/{name}-{profiles}.properties") public ResponseEntity<String> properties(@PathVariable String name, @PathVariable String profiles, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { return labelledProperties(name, profiles, null, resolvePlaceholders); }
這個路徑是直接獲取沒有label且配置檔案字尾為properties的資訊,labelledProperties()方法將在下面介紹。
4.@RequestMapping("/{label}/{name}-{profiles}.properties")
@RequestMapping("/{label}/{name}-{profiles}.properties") public ResponseEntity<String> labelledProperties(@PathVariable String name, @PathVariable String profiles, @PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException { validateProfiles(profiles); Environment environment = labelled(name, profiles, label); Map<String, Object> properties = convertToProperties(environment); String propertiesString = getPropertiesString(properties); if (resolvePlaceholders) { propertiesString = resolvePlaceholders(prepareEnvironment(environment), propertiesString); } return getSuccess(propertiesString); }
這個路徑是直接獲取包含label且配置檔案字尾為properties的資訊,首先校驗了profiles路徑是否包含了-,如果包含了直接返回錯誤,然後直接去labelled()方法獲取Environment物件,就是第二個請求路徑的方法,最後就是一系列的組裝解析資料的過程。
5.@RequestMapping("{name}-{profiles}.json")
@RequestMapping("{name}-{profiles}.json") public ResponseEntity<String> jsonProperties(@PathVariable String name, @PathVariable String profiles, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception { return labelledJsonProperties(name, profiles, null, resolvePlaceholders); }
這個路徑是直接獲取沒有label且配置檔案字尾為json的資訊,labelledJsonProperties()方法將在下面介紹。
6.@RequestMapping("/{label}/{name}-{profiles}.json")
@RequestMapping("/{label}/{name}-{profiles}.json") public ResponseEntity<String> labelledJsonProperties(@PathVariable String name, @PathVariable String profiles, @PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception { validateProfiles(profiles); Environment environment = labelled(name, profiles, label); Map<String, Object> properties = convertToMap(environment); String json = this.objectMapper.writeValueAsString(properties); if (resolvePlaceholders) { json = resolvePlaceholders(prepareEnvironment(environment), json); } return getSuccess(json, MediaType.APPLICATION_JSON); }
從方法實現上看validateProfiles()方法和labelled()方法和前面一樣,然後將Environment物件進行map轉換,然後通過ObjectMapper進行轉換,之後去除掉不必要的引數,用MediaType.APPLICATION_JSON的方式返回。
7.@RequestMapping({ "/{name}-{profiles}.yml", "/{name}-{profiles}.yaml" })
@RequestMapping({ "/{name}-{profiles}.yml", "/{name}-{profiles}.yaml" }) public ResponseEntity<String> yaml(@PathVariable String name, @PathVariable String profiles, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception { return labelledYaml(name, profiles, null, resolvePlaceholders); }
這個路徑對應兩種配置檔案路徑且不包含label,labelledYaml()方法將在下面介紹。
8.@RequestMapping({ "/{label}/{name}-{profiles}.yml",
"/{label}/{name}-{profiles}.yaml" })
@RequestMapping({ "/{label}/{name}-{profiles}.yml", "/{label}/{name}-{profiles}.yaml" }) public ResponseEntity<String> labelledYaml(@PathVariable String name, @PathVariable String profiles, @PathVariable String label, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws Exception { validateProfiles(profiles); Environment environment = labelled(name, profiles, label); Map<String, Object> result = convertToMap(environment); if (this.stripDocument && result.size() == 1 && result.keySet().iterator().next().equals("document")) { Object value = result.get("document"); if (value instanceof Collection) { return getSuccess(new Yaml().dumpAs(value, Tag.SEQ, FlowStyle.BLOCK)); } else { return getSuccess(new Yaml().dumpAs(value, Tag.STR, FlowStyle.BLOCK)); } } String yaml = new Yaml().dumpAsMap(result); if (resolvePlaceholders) { yaml = resolvePlaceholders(prepareEnvironment(environment), yaml); } return getSuccess(yaml); }
從方法實現上看validateProfiles()方法和labelled()方法和前面一樣,然後就是就行yml相關的操作,這裡不詳述了。
以上就主要介紹完了spring-cloud-config-server端的原始碼。