配置中心代码调用图:
config client入口是PropertySourceBootstrapConfiguration,最终会通过RestTemplate向config server发出http get请求,url包含了client的name、profile及lable信息。config server的EnvironmentController提供了多种参数形式的接口,然后调用EnvironmentRepository的findOne方法,根据client提供的参数获取属于它的配置信息,最终返回Environmen对象。具体代码如下:
PropertySourceBootstrapConfiguration类部分源码:
ConfigServicePropertySourceLocator部分源码:
剩下就是我们自己的代码了,首先要开启配置中心:
客户端直接在配置文件中开启并指定服务端名字,服务端在入口类加上:
然后就是实现EnvironmentRepository的findOne方法,如类EnvironmentRepositoryImpl
package com.ConfigServer.environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import com.ConfigServer.service.ConfigRepositoryService;
public class EnvironmentRepositoryImpl implements EnvironmentRepository{
/*
* 用于标识配置信息从哪里取,如database代表从数据库中取,properties代表从配置文件中取
* 为null代表从配置文件和数据库中取,该属性是自己定义的
*/
@Value("${spring.cloud.config.server.configSource}")
private String configSource;
@Autowired
private ConfigRepositoryService configRepositoryService;
/*
* 其它服务启动的时该方法会被调用,从而获得属于自己的配置信息
*/
@Override
public Environment findOne(String application, String profile, String label) {
switch (configSource) {
case "database":
return configRepositoryService.getEnvironmentFromDatabaseByMybatis(application, profile, label);
case "properties":
return configRepositoryService.getEnvironmentFromProperties(application, profile, label);
default:
// TODO 可以把数据库和配置文件并集
return null;
}
}
}
然后要注入EnvironmentRepositoryImpl类:
package com.ConfigServer.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.cloud.config.server.environment.NativeEnvironmentRepository;
import org.springframework.cloud.config.server.environment.SearchPathLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import com.ConfigServer.environment.EnvironmentRepositoryImpl;
@Configuration
public class EnvironmentRepositoryConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public SearchPathLocator searchPathLocator() {
return new NativeEnvironmentRepository(environment);
}
/*
* Primary与Order都是用来控制当有多个相同bean的实现时,选择哪一个。
* @Primary告诉容器该实现的优先级最高,作用与@Order(Ordered.HIGHEST_PRECEDENCE)相同,二选一即可
* 值得注意的是,如果Spring Cloud版本是Edgware.RELEASE,使用@Primary启动会报错,此时可以使用Order,
* 两者都不用似乎也可以
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
// @Primary
@Bean
public EnvironmentRepository openEnvironmentRepository() {
return new EnvironmentRepositoryImpl();
}
}
为什么还要注入NativeEnvironmentRepository?有待研究
这里虽然EnvironmentRepositoryImpl的优先级最高,但其依赖ConfigRepositoryService,所以还是会先注入ConfigRepositoryService,该类用于读取配置信息(如从数据库获取):
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.config.environment.Environment;
import org.springframework.cloud.config.environment.PropertySource;
import org.springframework.context.EnvironmentAware;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Service;
import com.ConfigServer.entity.NameValuePair;
@Service
public class ConfigRepositoryService implements EnvironmentAware{
private org.springframework.core.env.Environment env;
@Autowired
private JdbcTemplate jdbcTemplate;
//也可以采用如下方式注入Environment,而不用实现EnvironmentAware
// @Autowired
// private ConfigurableEnvironment env;
@Override
public void setEnvironment(org.springframework.core.env.Environment environment) {
env = environment;
}
public Environment getEnvironmentFromProperties(String application, String profile, String label){
//如果属性个数很少且名字确定,也可以用@Value的方式注入
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env,application+".");
Map<String, Object> propertyMap = propertyResolver.getSubProperties(null);
PropertySource propertySource = new PropertySource("configServer", propertyMap);
Environment environment = new Environment(application, profile, label, "1.0", null);
environment.add(propertySource);
return environment;
}
public Environment getEnvironmentFromDatabase(String application, String profile, String label) {
ResultSetExtractor<List<NameValuePair>> rse = new ResultSetExtractor<List<NameValuePair>>() {
@Override
public List<NameValuePair> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
while (rs.next()) {
NameValuePair nameValuePair = new NameValuePair();
nameValuePair.setName(rs.getString("key"));
nameValuePair.setValue(rs.getString("value"));
nameValuePairs.add(nameValuePair);
}
return nameValuePairs;
}
};
String sql = "select key , value from config_info where application=?";
Object[] params = new Object[1];
params[0] = application;
List<NameValuePair> nameValuePairs = jdbcTemplate.query(sql, params, rse);
if(nameValuePairs == null || nameValuePairs.size() == 0)
return null;
Map<String, Object> propertyMap = new HashMap<String, Object>();
for(NameValuePair nameValuePair:nameValuePairs) {
propertyMap.put(nameValuePair.getName(), nameValuePair.getValue());
}
PropertySource propertySource = new PropertySource("configServer", propertyMap);
Environment environment = new Environment(application, profile, label, "1.0", null);
environment.add(propertySource);
return environment;
}
}
Environment可以add多个propertySource,propertySource的名字仅仅是用于标识这些配置项的来源,可以任意取,比如,属于数据库配置项,我们可以取名oralce.db,属于定时任务的配置项,我们可以取名schedule等等。