美文网首页
Spring Cloud Config 的交互流程

Spring Cloud Config 的交互流程

作者: 蓝笔头 | 来源:发表于2020-07-24 21:56 被阅读0次

    1. demo 搭建

    2. config-client 源码解析

    直接看 ConfigServicePropertySourceLocator 中从 configserver 获取配置的相关代码。

    package org.springframework.cloud.config.client;
    
    
    @Order(0)
    public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
    
        @Override
        @Retryable(interceptor = "configServerRetryInterceptor")
        public org.springframework.core.env.PropertySource<?> locate(
                org.springframework.core.env.Environment environment) {
            ConfigClientProperties properties = this.defaultProperties.override(environment);
            CompositePropertySource composite = new OriginTrackedCompositePropertySource(
                    "configService");
            RestTemplate restTemplate = this.restTemplate == null
                    ? getSecureRestTemplate(properties) : this.restTemplate;
            try {
                String[] labels = new String[] { "" };
                if (StringUtils.hasText(properties.getLabel())) {
                    labels = StringUtils
                            .commaDelimitedListToStringArray(properties.getLabel());
                }
                String state = ConfigClientStateHolder.getState();
                // Try all the labels until one works
                for (String label : labels) {
                    // 1. 从 configserver 获取配置
                    Environment result = getRemoteEnvironment(restTemplate, properties,
                            label.trim(), state);
                    if (result != null) {
                        log(result);
    
                        // result.getPropertySources() can be null if using xml
                        if (result.getPropertySources() != null) {
                            // 配置通过 Environment 中的 PropertySource 字段返回
                            for (PropertySource source : result.getPropertySources()) {
                                @SuppressWarnings("unchecked")
                                Map<String, Object> map = translateOrigins(source.getName(),
                                        (Map<String, Object>) source.getSource());
                                composite.addPropertySource(
                                        new OriginTrackedMapPropertySource(source.getName(),
                                                map));
                            }
                        }
    
                        if (StringUtils.hasText(result.getState())
                                || StringUtils.hasText(result.getVersion())) {
                            HashMap<String, Object> map = new HashMap<>();
                            putValue(map, "config.client.state", result.getState());
                            putValue(map, "config.client.version", result.getVersion());
                            composite.addFirstPropertySource(
                                    new MapPropertySource("configClient", map));
                        }
                        return composite;
                    }
                }
            }
        }
        
        private Environment getRemoteEnvironment(RestTemplate restTemplate,
                ConfigClientProperties properties, String label, String state) {
            String path = "/{name}/{profile}";
            String name = properties.getName();
            String profile = properties.getProfile();
            String token = properties.getToken();
            int noOfUrls = properties.getUri().length;
            if (noOfUrls > 1) {
                logger.info("Multiple Config Server Urls found listed.");
            }
    
            Object[] args = new String[] { name, profile };
            if (StringUtils.hasText(label)) {
                if (label.contains("/")) {
                    label = label.replace("/", "(_)");
                }
                args = new String[] { name, profile, label };
                path = path + "/{label}";
            }
            ResponseEntity<Environment> response = null;
    
            for (int i = 0; i < noOfUrls; i++) {
                Credentials credentials = properties.getCredentials(i);
                String uri = credentials.getUri();
                String username = credentials.getUsername();
                String password = credentials.getPassword();
    
                logger.info("Fetching config from server at : " + uri);
    
                try {
                    HttpHeaders headers = new HttpHeaders();
                    // 设置 Accept 为 "application/vnd.spring-cloud.config-server.v2+json",用来匹配 configserver 中的接口的 @RequestMapping 注解的 produces 属性
                    headers.setAccept(
                            Collections.singletonList(MediaType.parseMediaType(V2_JSON)));
                    addAuthorizationToken(properties, headers, username, password);
                    if (StringUtils.hasText(token)) {
                        headers.add(TOKEN_HEADER, token);
                    }
                    if (StringUtils.hasText(state) && properties.isSendState()) {
                        headers.add(STATE_HEADER, state);
                    }
    
                    final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
                    // 2. 没有 label 的情况下,调用 configserver 的 /{name}/{profile} 接口户获取配置信息,配置信息通过 Environment 类型返回
                    response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
                            Environment.class, args);
                }
                Environment result = response.getBody();
                return result;
            }
    
            return null;
        }
    }
    

    从上述代码中,我们可以得知,config-client 是通过调用 configserver/{name}/{profile} 接口户获取配置信息。

    3. configserver 源码分析

    3.1 EnvironmentController

    package org.springframework.cloud.config.server.environment;
    
    
    @RestController
    @RequestMapping(method = RequestMethod.GET,
            path = "${spring.cloud.config.server.prefix:}")
    public class EnvironmentController {
    
        private EnvironmentRepository repository;
    
        @RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
                produces = MediaType.APPLICATION_JSON_VALUE)
        public Environment defaultLabel(@PathVariable String name,
                @PathVariable String profiles) {
            return getEnvironment(name, profiles, null, false);
        }
    
        // config-client 调用此接口
        // Accept 为 "application/vnd.spring-cloud.config-server.v2+json" 匹配 produces = EnvironmentMediaType.V2_JSON
        @RequestMapping(path = "/{name}/{profiles:.*[^-].*}",
                produces = EnvironmentMediaType.V2_JSON)
        public Environment defaultLabelIncludeOrigin(@PathVariable String name,
                @PathVariable String profiles) {
            return getEnvironment(name, profiles, null, true);
        }
    
        public Environment getEnvironment(String name, String profiles, String label,
                boolean includeOrigin) {
            name = normalize(name);
            label = normalize(label);
            Environment environment = this.repository.findOne(name, profiles, label,
                    includeOrigin);
            if (!this.acceptEmpty
                    && (environment == null || environment.getPropertySources().isEmpty())) {
                throw new EnvironmentNotFoundException("Profile Not found");
            }
            return environment;
        }
    
    }
    

    后续测试可以通过手动调用 http://localhost:8888/application/dev 接口,来触发 configserver 的 getEnvironment 逻辑。

    3.2 NativeEnvironmentRepository

    getEnvironment debug 截图.png

    根据 debug 查看上下文,发现最终是通过 NativeEnvironmentRepository 类来生成 Environment 的。

    package org.springframework.cloud.config.server.environment;
    
    public class NativeEnvironmentRepository
            implements EnvironmentRepository, SearchPathLocator, Ordered {
    
        private static final String[] DEFAULT_LOCATIONS = new String[] { "classpath:/",
                "classpath:/config/", "file:./", "file:./config/" };
    
        /**
         * Locations to search for configuration files. Defaults to the same as a Spring Boot
         * app so [classpath:/,classpath:/config/,file:./,file:./config/].
         */
        private String[] searchLocations;
    
    
        private ConfigurableEnvironment environment;
    
    
        @Override
        public Environment findOne(String config, String profile, String label,
                boolean includeOrigin) {
            SpringApplicationBuilder builder = new SpringApplicationBuilder(
                    PropertyPlaceholderAutoConfiguration.class);
            ConfigurableEnvironment environment = getEnvironment(profile);
            builder.environment(environment);
            builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
            if (!logger.isDebugEnabled()) {
                // Make the mini-application startup less verbose
                builder.logStartupInfo(false);
            }
            
            String[] args = getArgs(config, profile, label);
            // Explicitly set the listeners (to exclude logging listener which would change
            // log levels in the caller)
            builder.application()
                    .setListeners(Arrays.asList(new ConfigFileApplicationListener()));
            
            // ConfigFileApplicationListener 中加载 spring.config.location 和 spring.config.name 参数的匹配的配置到 environment 中
            try (ConfigurableApplicationContext context = builder.run(args)) {
                environment.getPropertySources().remove("profiles");
                // PassthruEnvironmentRepository.findOne 过滤一些系统相关的配置
                // clean 中过滤不匹配的配置
                // 最后返回符号条件的配置
                return clean(new PassthruEnvironmentRepository(environment).findOne(config,
                        profile, label, includeOrigin));
            }
        }
    
        private String[] getArgs(String application, String profile, String label) {
            List<String> list = new ArrayList<String>();
            String config = application;
            if (!config.startsWith("application")) {
                config = "application," + config;
            }
            list.add("--spring.config.name=" + config);
            list.add("--spring.cloud.bootstrap.enabled=false");
            list.add("--encrypt.failOnError=" + this.failOnError);
            list.add("--spring.config.location=" + StringUtils.arrayToCommaDelimitedString(
                    getLocations(application, profile, label).getLocations()));
            return list.toArray(new String[0]);
        }
    }
    

    (完)

    相关文章

      网友评论

          本文标题:Spring Cloud Config 的交互流程

          本文链接:https://www.haomeiwen.com/subject/mduslktx.html