Spring Cloud Config组件作为微服务的配置中心,可以为各个应用提供集中的配置中心,使用起来还是比较方便的。现在就来分析一波实现原理。

Config组件主要由客户端和服务端组成。客户端可以单独作为一个微服务,引入依赖如下:

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-config-server</artifactId>
		</dependency>

客户端主要是在EurekaCilent端引入依赖,和服务端做关联,依赖如下:

<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-config</artifactId>
		</dependency>

我们先从客户端做分析,毕竟服务端的内容比较简单。

spring-cloud-starter-config依赖中包含父依赖spring-cloud-config


<parent>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-config</artifactId>
   <version>1.4.2.RELEASE</version>
</parent>


spring-cloud-config中又包含很多子模块


<modules>
   <module>spring-cloud-config-dependencies</module>
   <module>spring-cloud-config-client</module>
   <module>spring-cloud-config-server</module>
   <module>spring-cloud-config-monitor</module>
   <module>spring-cloud-config-sample</module>
   <module>spring-cloud-starter-config</module>
   <module>docs</module>
</modules>


spring-cloud-config-client的META-INF内容包括

spring cloud config client 拉取配置的逻辑代码_spring boot

打开spring.factories文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.config.client.ConfigClientAutoConfiguration

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

可以看到自动注入了ConfigClientAutoConfiguration、ConfigServiceBootstrapConfiguration、DiscoveryClientConfigServiceBootstrapConfiguration三个类。这里额外分析下BootstrapConfiguration和EnableAutoConfiguration装载过程。大师可以直接跳过。

========================================================================================

在启动Spring Cloud服务时,导入的依赖中包含spring-cloud-context模块。其中META-INF文件夹下spring.factories文件内容:

# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

可以看到里面包含了ApplicationListener和BootstrapConfiguration两个类所包含的加载类。我们在启动应用的时候都会执行SpringApplication.run(EurekaclientApplication.class, args);方法。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

new SpringApplication(sources)方法会直接调用initialize(sources)方法。

private void initialize(Object[] sources) {
	if (sources != null && sources.length > 0) {
	this.sources.addAll(Arrays.asList(sources));
	}
	this.webEnvironment = deduceWebEnvironment();
	setInitializers((Collection) getSpringFactoriesInstances(
		ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

进入setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));方法。

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<String>(
			SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
			classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

进入SpringFactoriesLoader.loadFactoryNames(type, classLoader))方法。

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

我们可以看到将所有资源文件spring.factories中类型是ApplicationListener类都装载了进来。接着继续执行SpringApplication.run(EurekaclientApplication.class, args);

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

直接看ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法。里面执行listener.environmentPrepared方法。会直接调用BootstrapApplicationListener.onApplicationEvent方法。

public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
				true)) {
			return;
		}
		// don't listen to events in a bootstrap context
		if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			return;
		}
		ConfigurableApplicationContext context = null;
		String configName = environment
				.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
		for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
				.getInitializers()) {
			if (initializer instanceof ParentContextApplicationContextInitializer) {
				context = findBootstrapContext(
						(ParentContextApplicationContextInitializer) initializer,
						configName);
			}
		}
		if (context == null) {
			context = bootstrapServiceContext(environment, event.getSpringApplication(),
					configName);
		}
		apply(context, event.getSpringApplication(), environment);
	}

直接看context = bootstrapServiceContext(environment, event.getSpringApplication(), configName);方法。

private ConfigurableApplicationContext bootstrapServiceContext(
			ConfigurableEnvironment environment, final SpringApplication application,
			String configName) {
		...........................
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		List<String> names = SpringFactoriesLoader
				.loadFactoryNames(BootstrapConfiguration.class, classLoader);
		for (String name : StringUtils.commaDelimitedListToStringArray(
				environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
			names.add(name);
		}
		..........................
		return context;
	}

可以看到BootstrapApplicationListener.onApplicationEvent方法又把spring.factories中类型是BootstrapConfiguration

类都装载了进来。

结论:SpringBoot装载顺序为

1、通过加载spring-boot模块的SpringApplicationRunListener对应类EventPublishingRunListener,将SpringApplication加载的META-INF中spring.factories类型为ApplicationListener的对象全部加载进来。

2、通过SpringApplication的run方法,触发准备阶段监听事件,将META-INF中spring.factories类型为BootstrapConfiguration的对象全部加载进来。

3、bootstrap.properties(yml)的加载是先于application.properties(yml)的。

=======================================================================================

分析上面的加载过程只是为了更好的梳理spring-config加载顺序。继续回到spring-cloud-config-client模块中。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.config.client.ConfigClientAutoConfiguration

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

ConfigServiceBootstrapConfiguration类中的详细信息如下:

@Configuration
@EnableConfigurationProperties
public class ConfigServiceBootstrapConfiguration {
    @Autowired
    private ConfigurableEnvironment environment;

    public ConfigServiceBootstrapConfiguration() {
    }

    @Bean
    public ConfigClientProperties configClientProperties() {
        ConfigClientProperties client = new ConfigClientProperties(this.environment);
        return client;
    }

    @Bean
    @ConditionalOnMissingBean({ConfigServicePropertySourceLocator.class})
    @ConditionalOnProperty(
        value = {"spring.cloud.config.enabled"},
        matchIfMissing = true
    )
    public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
        ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(properties);
        return locator;
    }
    ..................
}

主要包含了spring-config的client配置信息ConfigClientProperties。

@ConfigurationProperties("spring.cloud.config")
public class ConfigClientProperties {
    public static final String PREFIX = "spring.cloud.config";
    public static final String TOKEN_HEADER = "X-Config-Token";
    public static final String STATE_HEADER = "X-Config-State";
    private boolean enabled = true;
    private String profile = "default";
    @Value("${spring.application.name:application}")
    private String name;
    private String label;
    private String username;
    private String password;
    private String uri = "http://localhost:8888";
    private ConfigClientProperties.Discovery discovery = new ConfigClientProperties.Discovery();
    private boolean failFast = false;
    private String token;
    private String authorization;
    ............................

    public static class Discovery {
        public static final String DEFAULT_CONFIG_SERVER = "configserver";
        private boolean enabled;
        private String serviceId = "configserver";

        public Discovery() {
        }

        public boolean isEnabled() {
            return this.enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public String getServiceId() {
            return this.serviceId;
        }

        public void setServiceId(String serviceId) {
            this.serviceId = serviceId;
        }
    }
    ..................
}

从中我们可以看到,spring-config-server的默认端口是8888,默认的serviceId是configserver。ConfigServiceBootstrapConfiguration类中还包含了一个重要的服务配置ConfigServicePropertySourceLocator对象。

@Bean
    @ConditionalOnMissingBean({ConfigServicePropertySourceLocator.class})
    @ConditionalOnProperty(
        value = {"spring.cloud.config.enabled"},
        matchIfMissing = true
    )
    public ConfigServicePropertySourceLocator configServicePropertySource(ConfigClientProperties properties) {
        ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(properties);
        return locator;
    }

该对象的locate方法会在springboot加载时触发。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();

        try {
           ...............
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 
            .............
        }
}
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        ..............
        this.applyInitializers(context);
        ................
    }
protected void applyInitializers(ConfigurableApplicationContext context) {
        Iterator var2 = this.getInitializers().iterator();

        while(var2.hasNext()) {
            ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

该方法会执行PropertySourceBootstrapConfiguration的initialize方法。

@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME
			+ "Properties";

	@Autowired(required = false)
	private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();


	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		CompositePropertySource composite = new CompositePropertySource(
				BOOTSTRAP_PROPERTY_SOURCE_NAME);
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		boolean empty = true;
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			PropertySource<?> source = null;
			source = locator.locate(environment);
			if (source == null) {
				continue;
			}
			logger.info("Located property source: " + source);
			composite.addPropertySource(source);
			empty = false;
		}
		if (!empty) {
			MutablePropertySources propertySources = environment.getPropertySources();
			String logConfig = environment.resolvePlaceholders("${logging.config:}");
			LogFile logFile = LogFile.get(environment);
			if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
				propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
			}
			insertPropertySources(propertySources, composite);
			reinitializeLoggingSystem(environment, logConfig, logFile);
			setLogLevels(applicationContext, environment);
			handleIncludedProfiles(environment);
		}
	}
}

该Bean会注入 List<PropertySourceLocator> propertySourceLocators。而ConfigServicePropertySourceLocator也是实现了PropertySourceLocator接口。所以会执行ConfigServicePropertySourceLocator.locate方法。

public PropertySource<?> locate(Environment environment) {
        //获取config配置信息
        ConfigClientProperties properties = this.defaultProperties.override(environment);
        CompositePropertySource composite = new CompositePropertySource("configService");
        RestTemplate restTemplate = this.restTemplate == null ? this.getSecureRestTemplate(properties) : this.restTemplate;
        Exception error = null;
        String errorBody = null;
        //从服务器拉取
        logger.info("Fetching config from server at: " + properties.getRawUri());

        try {
            String[] labels = new String[]{""};
            if (StringUtils.hasText(properties.getLabel())) {
                labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
            }

            String state = ConfigClientStateHolder.getState();
            String[] var9 = labels;
            int var10 = labels.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                String label = var9[var11];
                //开始调用,获取结果对象
                org.springframework.cloud.config.environment.Environment result = this.getRemoteEnvironment(restTemplate, properties, label.trim(), state);
                if (result != null) {
                    logger.info(String.format("Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", result.getName(), result.getProfiles() == null ? "" : Arrays.asList(result.getProfiles()), result.getLabel(), result.getVersion(), result.getState()));
                    if (result.getPropertySources() != null) {
                        Iterator var14 = result.getPropertySources().iterator();

                        while(var14.hasNext()) {
                            org.springframework.cloud.config.environment.PropertySource source = (org.springframework.cloud.config.environment.PropertySource)var14.next();
                            Map<String, Object> map = source.getSource();
                            //结果对象放入Map,便于其他Bean对象注入@Value
                            composite.addPropertySource(new MapPropertySource(source.getName(), map));
                        }
                    }

                    if (StringUtils.hasText(result.getState()) || StringUtils.hasText(result.getVersion())) {
                        HashMap<String, Object> map = new HashMap();
                        this.putValue(map, "config.client.state", result.getState());
                        this.putValue(map, "config.client.version", result.getVersion());
                        composite.addFirstPropertySource(new MapPropertySource("configClient", map));
                    }

                    return composite;
                }
            }
        } 
    }

主要就是从ConfigClientProperties中获取ConfigServer的服务器地址信息等,作为交互的配置。之后从config服务获取到配置文件中的各种配置信息后,放入map中,供本地Bean对象的注入使用。

接下来我们继续分析自动注入中第二个比较有用的对象DiscoveryClientConfigServiceBootstrapConfiguration。

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

DiscoveryClientConfigServiceBootstrapConfiguration比较简单。

@Configuration
@Import({UtilAutoConfiguration.class})
@EnableDiscoveryClient
public class DiscoveryClientConfigServiceBootstrapConfiguration {
    private static Log logger = LogFactory.getLog(DiscoveryClientConfigServiceBootstrapConfiguration.class);
    @Autowired
    private ConfigClientProperties config;
    @Autowired
    private ConfigServerInstanceProvider instanceProvider;
    private HeartbeatMonitor monitor = new HeartbeatMonitor();

    public DiscoveryClientConfigServiceBootstrapConfiguration() {
    }

    @Bean
    public ConfigServerInstanceProvider configServerInstanceProvider(DiscoveryClient discoveryClient) {
        return new ConfigServerInstanceProvider(discoveryClient);
    }

    @EventListener({ContextRefreshedEvent.class})
    public void startup(ContextRefreshedEvent event) {
        this.refresh();
    }

    @EventListener({HeartbeatEvent.class})
    public void heartbeat(HeartbeatEvent event) {
        if (this.monitor.update(event.getValue())) {
            this.refresh();
        }

    }

    private void refresh() {
        try {
            String serviceId = this.config.getDiscovery().getServiceId();
            ServiceInstance server = this.instanceProvider.getConfigServerInstance(serviceId);
            String url = this.getHomePage(server);
            String path;
            if (server.getMetadata().containsKey("password")) {
                path = (String)server.getMetadata().get("user");
                path = path == null ? "user" : path;
                this.config.setUsername(path);
                String password = (String)server.getMetadata().get("password");
                this.config.setPassword(password);
            }

            if (server.getMetadata().containsKey("configPath")) {
                path = (String)server.getMetadata().get("configPath");
                if (url.endsWith("/") && path.startsWith("/")) {
                    url = url.substring(0, url.length() - 1);
                }

                url = url + path;
            }

            this.config.setUri(url);
        } catch (Exception var6) {
            if (this.config.isFailFast()) {
                throw var6;
            }

            logger.warn("Could not locate configserver via discovery", var6);
        }
    }
}

DiscoveryClientConfigServiceBootstrapConfiguration自动注入了刚才生成的ConfigServer的配置ConfigClientProperties对象。另外需要注意类中的这段代码。

@EventListener({ContextRefreshedEvent.class})
    public void startup(ContextRefreshedEvent event) {
        this.refresh();
    }

@EventListener的作用是,该Bean完成createBean后,会将Bean中包含EventListener注解的方法提取出来,作为spring的通知Listener。在refresh()方法中,我们可以看到通过ServiceId找到EurekaServer中包含该ServiceId的应用服务实例,之后将该服务的url信息重新设置为对应的ip。

这样我们在调用的时候就会发现 Fetching config from server at: " + properties.getRawUri());这句话已经直接改为调用EurekaServer服务中对应ConfigServer服务器的地址信息了。

以上就是对Spring-Cloud-Config的简单分析。未完待续。。。。