配置中心的配置内容可以来自自己的配置文件、数据库、Git及Subversion,本文只描述前面3种的用法。
例子码云地址:
配置中心:https://gitee.com/wudiyong/ConfigServer.git
其它客户端:https://gitee.com/wudiyong/userInfoService.git
配置内容来自配置文件、数据库
1、pom.xml文件配置
首先配置中心是一个Eureka客户端,所以要加入Eureka依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
还要加入配置中心依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
pom.xml文件全部内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>ConfigServer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ConfigServer</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意:
1)如果不需要连接数据库,则把数据库有关的依赖删掉,否则启动会报错
2)如果需要用到数据库,还要加上数据库驱动依赖,如mysql可加上如下依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
如果是oracle则要注意,因为oracle授权原因,其驱动不能通过maven引入,当然,你可以自己定义maven仓库,然后把其放到里面,最简单的方式是到网上下载然后通过build path方式引入,如果你安装了oracle数据库,可以在安装路径中找到该驱动包,名字为ojdbc6.jar。
2、application.properties文件配置
配置中心本身就是一个Eureka客户端,所以需要向Eureka服务器注册:
spring.application.name=config-server
server.port=8999
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
如果不需要连接数据库,则不用做其它配置了,如果要连接数据库,则加入如下配置:
#spring.cloud.config.server.configSource=properties
spring.cloud.config.server.configSource=database
###########以下是配置中心的数据库配置信息###########
config.oracle.db.jdbc_driverClassName=oracle.jdbc.OracleDriver
config.oracle.db.jdbc_url=jdbc:oracle:thin:@localhost:1522:db001
config.oracle.db.jdbc_user=db001data
config.oracle.db.jdbc_password=123456
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat,wall,log4j
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.cloud.config.server.configSource是自定义的属性,用于配置通过哪种方式读取配置内容。这里采用spring的JdbcTemplate方式来访问数据库,如果要结合mybatis,请看spring boot结合mybatis文章。
下面随便写一些要被读取的配置内容:
##############服务userInfo-service的配置信息####################
userInfo-service.config-server.database.datasource=ds1,ds2
userInfo-service.config-server.database.ds1.jdbc_driverClassName=oracle.jdbc.OracleDriver
userInfo-service.config-server.database.ds1.jdbc_url=jdbc:oracle:thin:@xx.xx.xx.xx:1521:xxxx
userInfo-service.config-server.database.ds1.jdbc_user=username
userInfo-service.config-server.database.ds1.jdbc_password=password
userInfo-service.config-server.database.ds2.jdbc_driverClassName=oracle.jdbc.OracleDriver
userInfo-service.config-server.database.ds2.jdbc_url=jdbc:oracle:thin:@xx.xx.xx.xx:1521:xxxx
userInfo-service.config-server.database.ds2.jdbc_user=username
userInfo-service.config-server.database.ds2.jdbc_password=password
userInfo-service.config-server.others.property1=property1Value
userInfo-service.config-server.others.property2=property2Value
属性的名字随意,为了区分这些属性属于哪个服务,通常会以服务名开头,如userInfo-service,因为一个服务(非配置中心)启动时,会调配置中心的EnvironmentRepository的实现类的public Environment findOne(String application, String profile, String label)方法,所以,我们可以根据这三个来自调用方的属性来读取属于它的配置信息。
application的值来自调用方的spring.application.name属性
profile的值来自调用方的spring.cloud.config.profile
label的值来自调用方的spring.cloud.config.label
3、Java文件
1)入口类加上如下注解:
@EnableConfigServer
@SpringBootApplication
@EnableEurekaClient
2)实现EnvironmentRepository接口:
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;
}
}
}
3)编写配置类,加载自定义的EnvironmentRepository实现类
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();
}
}
4)编写读取配置内容的类
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;
}
}
public class NameValuePair {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
5)如果连接数据库,则编写配置类,加载自定义DataSource(这里用的是com.alibaba.druid.pool.DruidDataSource连接池数据源)及JdbcTemplate:
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import com.alibaba.druid.pool.DruidDataSource;
@Configuration
public class DruidDBConfig {
private Logger log = LoggerFactory.getLogger(DruidDBConfig.class);
@Value("${config.oracle.db.jdbc_url}")
private String dbUrl;
@Value("${config.oracle.db.jdbc_user}")
private String username;
@Value("${config.oracle.db.jdbc_password}")
private String password;
@Value("${config.oracle.db.jdbc_driverClassName}")
private String driverClassName;
//以下根据需要配置及调整
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.filters}")
private String filters;
@Value("{spring.datasource.connectionProperties}")
private String connectionProperties;
@Bean
@Primary
public DataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(this.dbUrl);
datasource.setUsername(this.username);
datasource.setPassword(this.password);
datasource.setDriverClassName(this.driverClassName);
// configuration
datasource.setInitialSize(this.initialSize);
datasource.setMinIdle(this.minIdle);
datasource.setMaxActive(this.maxActive);
datasource.setMaxWait(this.maxWait);
datasource.setTimeBetweenEvictionRunsMillis(this.timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(this.minEvictableIdleTimeMillis);
datasource.setValidationQuery(this.validationQuery);
datasource.setTestWhileIdle(this.testWhileIdle);
datasource.setTestOnBorrow(this.testOnBorrow);
datasource.setTestOnReturn(this.testOnReturn);
datasource.setPoolPreparedStatements(this.poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(this.maxPoolPreparedStatementPerConnectionSize);
try {
datasource.setFilters(this.filters);
} catch (SQLException e) {
log.error("druid configuration initialization filter", e);
}
datasource.setConnectionProperties(this.connectionProperties);
return datasource;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate jdbcTemplate( @Qualifier("dataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
数据库表config_info内容如下:
至此,配置中心已搭建完成,下面是其它Eureka客户端通过配置中心获取配置信息:
1、pom.xml加上如下配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2、在bootstrap.properties中加入如下配置:
#配置中心,需要放在bootstrap.properties中才能生效
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.profile=dev
spring.cloud.config.label=master
config-server是配置中心的名字
3、完成前两步后,服务在启动的时候已经获取到了配置中心的配置内容了,通过配置中心方式获取的配置信息存放在Environment对象中,该方式与本地配置文件方式效果一样。
本地配置方式中,如:
aaa.bbb.ccc=abc
xxxx=${aaa.bbb.ccc}123
则读取xxxx的值为abc123
同样的,假如通过配置中心的方式获取到xxxx的值为${}123,也会被替换成abc123,总之,配置中心与本地配置文件是同样效果的。
所以,可以通过如下方式获取配置项:
1、在属性前面使用@Value("${xxxx.xx.xxx}")
2、通过Environment获取
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import com.userInfoService.entity.PropertyEntity;
@Service
public class EnvironmentService implements EnvironmentAware{
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public List<PropertyEntity> getDBConfig() {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment,"config-server.database.");
String dsPrefixs = propertyResolver.getProperty("datasource");
if(dsPrefixs == null || "".equals(dsPrefixs))
return null;
List<PropertyEntity> propertyList = new ArrayList<PropertyEntity>();
for(String dsPrefix:dsPrefixs.split(",")){
PropertyEntity propertyEntity = new PropertyEntity();
propertyEntity.setPrefix(dsPrefix);
Map<String, Object> propertyMap = propertyResolver.getSubProperties(dsPrefix+".");
propertyEntity.setProperties(propertyMap);
propertyList.add(propertyEntity);
}
return propertyList;
}
public PropertyEntity getOtherConfigs() {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment,"config-server.");
PropertyEntity propertyEntity = new PropertyEntity();
Map<String, Object> propertyMap = propertyResolver.getSubProperties("others.");
propertyEntity.setPrefix("others");
propertyEntity.setProperties(propertyMap);
return propertyEntity;
}
}
public class PropertyEntity {
private String prefix;
private Map<String, Object> properties;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Map<String, Object> getProperties() {
return properties;
}
public void setProperties(Map<String, Object> properties) {
this.properties = properties;
}
}
由于只在其它服务启动的时候会调一次配置中心的findOne方法,所以即使配置内容发生改变了,也不会重新获取最新内容,如果是Git或Subversion方式获取配置内容,则可以在其它服务的pom.xml文件加上如下配置来实现动态刷新配置内容:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
但由于上面是通过数据库和本地配置文件的方式,所以只能自己手动实现定时从配置中心读取配置信息,采用@Scheduled,下面是除配置中心外的其它服务的实现:
1)在启动类前面加上@EnableScheduling开启定时任务功能
2)编写定时任务,定时调用配置中心提供的接口
@Component
public class ConfigScheduled {
@Scheduled(fixedRate = 3000)//单位是毫秒
public void timerRate() {
//调用配置中心提供的获取配置信息接口
}
}
如何调用(看spring cloud的服务调用,可通过feign或RestTemplate)及如何更新自己的配置信息(怎么更新自己的Environment)省略。
配置中心在Controller类中,提供rest接口,当被调用时,就实时查询数据库,获取最新配置信息,如果配置中心是通过配置文件的方式,如何在配置文件修改时自动更新到自己的Environment中?目前我想到的方法是定时以读文件的方式读取配置文件内容,然后更新到Environment中。
其它Spring Cloud版本
如:
Spring Cloud采用Dalston.SR5,Spring Boot采用1.5.10.RELEASE
Spring Cloud采用Edgware.SR3,Spring Boot采用1.5.10.RELEASE
测试发现,如果采用Dalston.SR5,jar包下载不下来,而Edgware.SR3版本且没有问题。
配置内容来自Git及Subversion
后续补充。。。。