Spring Cloud Config 的交互流程

2020-07-24  本文已影响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]);
    }
}

(完)

上一篇下一篇

猜你喜欢

热点阅读