代码和配置都是程序的一部分,而一个完整、灵活的系统架构对于配置的管理是不可或缺的。根据程序实现业务需求的过程中不可避免的要使用一些可调换的配置项来控制代码在特定场景下的不同反应的状态,在不同的团队、不同的项目中大家用到的配置管理方案各有不同。
管理配置的技术大体有properties、spring xml配置文件配置,还有用于微服务的spring cloud
config技术等等。Java生态圈大家很多的配置方案都是建立在以上技术上的。把系统架构中的配置可分为两类:支持系统运行的基础配置,这些配置往往不会经常变动调整,至少不会在系统“运行”中更改,尽量不让开发人员修改的配置。比如:系统缓存配置,事务、一些基础支撑的中间件的配置。还有一类是业务配置这是比基础配置更高一级的配置项集合:这些配置项的特点是:跟业务流程代码相关性高但是又不合适做在特定的功能模块中的配置。比如说一个抽奖的功能,在用户获奖之后需要通知另一个在远程系统的方法(向指定URL发起请求),在开发、测试、生产各个过程的环境下调用的URL不一样,这样的情况下可以将此种配置定义为,业务配置。
这些配置放到properties或者spring xml配置文件中比较合适。 这种配置尽量要与代码离得近一些,结合的紧密一些。对于一个要用于项目中业务配置的管理我认为至少要满足一些要求:集中化、动态、灵活。要与代码隔离。
基础配置集合和业务配置集合要分开管理。因为大家在将基础配置与代码强关联之后,修改都需要重启生效,业务的配置要避免这样,减少开发调试的时间。
由以上需求做一个以数据库存放配置的实现方案。
把配置项放到数据库里可以避免修改配置重启程序才能生效的弊端,但是每次读取数据库中间消耗的时间也是很可观的,所以我们将配置集合放到一个Java集合中,每次应用配置项从程序内存读取减少对数据库的访问来提高效率,而在修改了某个配置项中我们需要动态的重载配置项,同时为了防止线程间的同步和安全问题,我们需要使用单例工厂模式实现管理一个线程间安全的集合。这个时候对于我们的这个工厂类必要的几个功能接口有,载入配置、数据库配置更改之后激发重载配置、读取单个配置项,读取所以配置项,当然,为了过于频繁(不正确的使用配置集合)的重载配置列表我们还要一个重载配置集合的事件间隔机制。
有了具体的需求剩下的就是实现了。
依赖
Java 1.8 +
Spring 5
Lombok
Mybatis / Mybatis Plus / JPA
项目代码仓库 - >
import java.util.Properties;
// Properties工具接口
public interface PropertiesUtil {
public Properties getProperties();
public String getProperty(String key);
public Object setProperty(String key, String value);
public Long getPropertyByLong(String key);
}
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Properties配置工具类
*/
@Slf4j
public class ApplicationPropertiesUtils implements PropertiesUtil {
private Properties properties;
/**
* 指定配置文件
*/
private static final String APPLICATIONCONFIGURATIONPROPERTIESFILENAME = "application.properties";
public ApplicationPropertiesUtils() {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(APPLICATIONCONFIGURATIONPROPERTIESFILENAME);
properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public ApplicationPropertiesUtils(String propertyPath) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(propertyPath);
properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public Properties getProperties() {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(APPLICATIONCONFIGURATIONPROPERTIESFILENAME);
properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
/**
* 读取配置项 - 字符串
* @param key
* @return
*/
@Override
public String getProperty(String key) {
return this.properties.getProperty(key);
}
@Override
public Object setProperty(String key, String value) {
return this.properties.setProperty(key, value);
}
/**
* 读取配置 - 长整形
* @param key
* @return
*/
@Override
public Long getPropertyByLong(String key) {
String temp = this.properties.getProperty(key);
for (int i = 0; i < temp.length()-1; i++) {
if ( ! Character.isDigit(temp.charAt(i))) {
log.error("getPropertyByLong 配置项的value出现非数字:"+key);
return null;
}
}
return Long.parseLong(temp);
}
}
Mysql
create table if not exists applicationconfiguration
(
updateTimeAC datetime not null comment '配置更新时间',
keyAC varchar(128) charset utf8 not null comment '配置项键',
valueAC varchar(256) charset utf8 not null comment '配置项值',
authorAC varchar(16) charset utf8 not null comment '更新/创建人',
descriptionAC varchar(256) charset utf8 not null comment '配置说明',
constraint ApplicationConfiguration_keyAC_uindex
unique (keyAC)
)
comment '程序配置表' charset=utf8mb4;
alter table applicationconfiguration
add primary key (keyAC);
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.type.JdbcType;
import javax.persistence.Entity;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@TableName("applicationconfiguration")
public class ApplicationConfiguration implements Serializable {
@TableId(value = "keyAC" )
String keyAC;
@TableField(value = "valueAC" ,jdbcType = JdbcType.NVARCHAR)
String valueAC;
@TableField(value = "AuthorAC")
String authorAC;
@TableField(value = "descriptionAC")
String descriptionAC;
@TableField(value = "updateTimeAC")
Date updateTimeAC;
}
@Repository
public interface ApplicationConfigurationDao extends BaseMapper<ApplicationConfiguration> {
List<ApplicationConfiguration> findAll();
}
因为是MyBatis实现要加一个Mapper配置。
<mapper namespace="cn.Dao.ApplicationConfigurationDao">
<select id="findAll"
resultType="cn.Entity.ApplicationConfiguration" useCache="true">
select *
from applicationconfiguration
select>
mapper>
/**
* 应用程序(项目)需要使用的配置单例工厂类
* add by 2019年12月8日 星期日
*/
@Slf4j
@Component
public class ApplicationConfigurationSingletonFactory {
// 配置DAO对象
@Autowired
private static ApplicationConfigurationDao applicationConfigurationDao;
// 配置集合(表)
private volatile static Hashtable hashtable = null;
// 最后一次载入配置时间
private volatile static Long lastLoadApplicationConfigurationTime ;
private volatile static ApplicationPropertiesUtils applicationPropertiesUtils = new ApplicationPropertiesUtils();
// 重载应用配置间隔时间
private volatile static Long ReloadApplicationConfigurationIntervalTime = 0L;
// 单例模式,将构造函数私有化
private ApplicationConfigurationSingletonFactory() {
}
// 静态代码块
static {
lastLoadApplicationConfigurationTime = 0L;
ReloadApplicationConfigurationIntervalTime = applicationPropertiesUtils.getPropertyByLong("ReloadApplicationConfigurationIntervalTime");
}
// 获取配置集合实例
public synchronized static Map getInstance(){
if (lastLoadApplicationConfigurationTime + ReloadApplicationConfigurationIntervalTime*1000 > System.currentTimeMillis()) {
log.warn("调用载入applicationConfiguration配置过于频繁,已超过设置间隔时间:"+ReloadApplicationConfigurationIntervalTime + "currentTimeMillis:"+System.currentTimeMillis());
throw new RuntimeException("调用载入applicationConfiguration配置过于频繁");
}
if (hashtable != null) {
lastLoadApplicationConfigurationTime = System.currentTimeMillis();
return hashtable;
}else{
if (applicationConfigurationDao != null) {
List<ApplicationConfiguration> applicationConfigurationList = applicationConfigurationDao.selectList(null);
hashtable = new Hashtable();
applicationConfigurationList.forEach(i->{
hashtable.put(i.getKeyAC(), i.getValueAC());
});
}else{
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
Object obj = wac.getBean(ApplicationConfigurationDao.class);
if (obj != null) {
applicationConfigurationDao = (ApplicationConfigurationDao) obj;
List<ApplicationConfiguration> applicationConfigurationList = applicationConfigurationDao.selectList(null);
hashtable = new Hashtable();
applicationConfigurationList.forEach(i->{
hashtable.put(i.getKeyAC(), i.getValueAC());
});
}else{
throw new RuntimeException("初始化异常:没有拿到实例化的applicationConfigurationDao对象" + ApplicationConfigurationSingletonFactory.class.getCanonicalName());
}
}
}
lastLoadApplicationConfigurationTime = System.currentTimeMillis();
return hashtable;
}
// 重载配置集合
public synchronized static Map reloadApplicationConfiguration(){
if (lastLoadApplicationConfigurationTime + ReloadApplicationConfigurationIntervalTime*1000 > System.currentTimeMillis()) {
log.warn("调用载入applicationConfiguration配置过于频繁,已超过设置间隔时间:"+ReloadApplicationConfigurationIntervalTime + "currentTimeMillis:"+System.currentTimeMillis());
throw new RuntimeException("调用载入applicationConfiguration配置过于频繁");
}
if (hashtable != null) {
hashtable.clear();
if (applicationConfigurationDao != null) {
List<ApplicationConfiguration> applicationConfigurationList = applicationConfigurationDao.selectList(null);
hashtable = new Hashtable();
applicationConfigurationList.forEach(i->{
hashtable.put(i.getKeyAC(), i.getValueAC());
});
}else{
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
Object obj = wac.getBean(ApplicationConfigurationDao.class);
if (obj != null) {
applicationConfigurationDao = (ApplicationConfigurationDao) obj;
List<ApplicationConfiguration> applicationConfigurationList = applicationConfigurationDao.selectList(null);
hashtable = new Hashtable();
applicationConfigurationList.forEach(i->{
hashtable.put(i.getKeyAC(), i.getValueAC());
});
}else{
throw new RuntimeException("初始化异常:没有拿到实例化的applicationConfigurationDao对象" + ApplicationConfigurationSingletonFactory.class.getCanonicalName());
}
}
}else{
log.warn("在重新载入应用程序配置之前没有使用getInstance():不推荐这样用。");
log.warn("尝试调用getInstance载入配置。。。");
Map result = ApplicationConfigurationSingletonFactory.getInstance();
lastLoadApplicationConfigurationTime = System.currentTimeMillis();
return result;
}
lastLoadApplicationConfigurationTime = System.currentTimeMillis();
return hashtable;
}
// 读取配置项(从单例的配置集合中) - 字符串
public static String getValueOfConfiguration(String key) {
Object temp = hashtable.get(key);
if (temp != null) {
return temp.toString();
}else{
log.error("没有找到配置项:"+key);
}
return null;
}
}
1、新建基础配置文件
这里我们使用application.properties
# 应用框架配置
# 重载一次applicationConfiguration的时间间隔 - 单位 / 秒
# add by 2019年12月8日 星期日
ReloadApplicationConfigurationIntervalTime=30
2、执行准备好的DDL建造表结构并插入一些测试数据
3、启动程序
测试Controller类,为了方便测试我们来写个Controller类
private String resultStr = new String();
/**
* 获取配置集合
* @return
*/
@RequestMapping("/getconfig")
@ResponseBody
public String getConfigProc(){
resultStr = "";
Map<String,String> list = ApplicationConfigurationSingletonFactory.getInstance();
list.forEach( (k,v)->{
resultStr +=v;
});
return resultStr;
}
/**
* 重载配置集合
* @return
*/
@RequestMapping("/reloadconfig")
@ResponseBody
public String getConfigProc2(){
resultStr = "";
Map<String,String> list = ApplicationConfigurationSingletonFactory.reloadApplicationConfiguration();
list.forEach( (k,v)->{
resultStr +=v;
});
return resultStr;
}
/**
* 读取指定配置项 - restful api
* @param key
* @return
*/
@RequestMapping("/getOneConfig/{key}")
@ResponseBody
public String getOneConfig(@PathVariable String key) {
String value = ApplicationConfigurationSingletonFactory.getValueOfConfiguration(key);
return value;
}
读取单个配置(此时还没有加载配置集合)
初始化配置集合(第一次读取配置到内存)
然后再次读取指定配置
第三次读取配置(因为没有重载配置集合,所以在内存中的配置集合还是修改前的)
重载配置集合(此操作会刷新内存中的配置集合)
第四次读取指定配置项(内存中的配置已经被刷新)
可以看到已经把修改过的配置加载过来了
因为我们设定了加载配置集合的间隔是30秒,如果在30秒内再次加载配置就会
这样可以避免不规范的配置集合操作。
以上实现给提供了一个,动态的,数据库集中管理配置的基础实现。如果大家有客制化需求可以方便的在此上实现,比如在spring mvc实例化bean时自动加载一次配置集合,后台任务定时刷新配置项等等功能。
程序启动运行代码,自动任务可以参考博主的其它文章哦:
Web应用启动自动带起代码的操作 ->
quartz任务框架基本原理及使用->