概述
我们需要一种机制,用于定义和声明配置项,并能够轻松地从配置文件(通常是YAML格式的文件)中加载用户自定义配置。每个配置项都具备一些关键属性,包括:
-
名称:每个配置项都有一个唯一的名称,通常对应一个字符串。这个名称用于标识配置项,确保不会与其他配置项发生冲突。
-
值:配置项的值是用户根据其需求指定的具体数值或数据。这些值可以是整数、浮点数、布尔值,甚至是复杂的数据结构,具体取决于配置项的类型。
-
配置项描述:为了让其他开发人员能够理解每个配置项的用途和含义,我们需要为它们提供描述文档。这将有助于维护和协作。
采用 "约定优于配置" 的原则是一种软件设计方法,它旨在降低开发人员需要做出的决策,从而获得更简洁的开发流程,但同时保持系统的灵活性。这种方式有助于减少配置的复杂性,通过一致的约定来简化软件的配置和部署,提高了开发者的效率。
总之,我们需要一种配置管理系统,它可以根据上述约定来定义、声明和加载配置项,从而使配置变得更加清晰和易于维护。
功能
-
配置项定义:开发人员可以轻松地定义配置项,提供配置名称、配置类型、默认值以及配置项的描述。这使得配置管理变得直观和可控。
-
配置项更新:系统应该支持在运行时更新配置项的值。这使得动态配置变得容易,无需重新启动应用程序即可生效。
-
配置文件加载:从预定义的路径中加载配置文件是一项重要功能。这个功能应该支持加载基本数据类型的配置,处理复杂数据类型,并且能够处理自定义结构体等高级数据类型。
-
配置项导出:为了帮助开发人员更好地理解和协作,系统还应该支持将当前项目的配置项导出。这可以是一个文档、配置文件或其他适当的格式,以便于项目的管理和维护
1、配置基类
class ConfigVarBase {
public:
typedef std::shared_ptr<ConfigVarBase> ptr;
/**
* @brief 构造函数
* @param[in] name 配置参数名称[0-9a-z_.]
* @param[in] description 配置参数描述
*/
ConfigVarBase(const std::string& name, const std::string& description = "")
:m_name(name)
,m_description(description) {
std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
}
/**
* @brief 析构函数
*/
virtual ~ConfigVarBase() {}
/**
* @brief 返回配置参数名称
*/
const std::string& getName() const { return m_name;}
/**
* @brief 返回配置参数的描述
*/
const std::string& getDescription() const { return m_description;}
/**
* @brief 转成字符串
*/
virtual std::string toString() = 0;
/**
* @brief 从字符串初始化值
*/
virtual bool fromString(const std::string& val) = 0;
/**
* @brief 返回配置参数值的类型名称
*/
virtual std::string getTypeName() const = 0;
protected:
/// 配置参数的名称
std::string m_name;
/// 配置参数的描述
std::string m_description;
};
2、配置项类
template<class T, class FromStr = LexicalCast<std::string, T>
,class ToStr = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase {
public:
typedef RWMutex RWMutexType;
typedef std::shared_ptr<ConfigVar> ptr;
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
/**
* @brief 通过参数名,参数值,描述构造ConfigVar
* @param[in] name 参数名称有效字符为[0-9a-z_.]
* @param[in] default_value 参数的默认值
* @param[in] description 参数的描述
*/
ConfigVar(const std::string& name
,const T& default_value
,const std::string& description = "")
:ConfigVarBase(name, description)
,m_val(default_value) {
}
/**
* @brief 将参数值转换成YAML String
* @exception 当转换失败抛出异常
*/
std::string toString() override {
try {
//return boost::lexical_cast<std::string>(m_val);
RWMutexType::ReadLock lock(m_mutex);
return ToStr()(m_val);
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception "
<< e.what() << " convert: " << TypeToName<T>() << " to string"
<< " name=" << m_name;
}
return "";
}
/**
* @brief 从YAML String 转成参数的值
* @exception 当转换失败抛出异常
*/
bool fromString(const std::string& val) override {
try {
setValue(FromStr()(val));
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
<< e.what() << " convert: string to " << TypeToName<T>()
<< " name=" << m_name
<< " - " << val;
}
return false;
}
/**
* @brief 获取当前参数的值
*/
const T getValue() {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}
/**
* @brief 设置当前参数的值
* @details 如果参数的值有发生变化,则通知对应的注册回调函数
*/
void setValue(const T& v) {
{
RWMutexType::ReadLock lock(m_mutex);
if(v == m_val) {
return;
}
for(auto& i : m_cbs) {
i.second(m_val, v);
}
}
RWMutexType::WriteLock lock(m_mutex);
m_val = v;
}
/**
* @brief 返回参数值的类型名称(typeinfo)
*/
std::string getTypeName() const override { return TypeToName<T>();}
/**
* @brief 添加变化回调函数
* @return 返回该回调函数对应的唯一id,用于删除回调
*/
uint64_t addListener(on_change_cb cb) {
static uint64_t s_fun_id = 0;
RWMutexType::WriteLock lock(m_mutex);
++s_fun_id;
m_cbs[s_fun_id] = cb;
return s_fun_id;
}
/**
* @brief 删除回调函数
* @param[in] key 回调函数的唯一id
*/
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}
/**
* @brief 获取回调函数
* @param[in] key 回调函数的唯一id
* @return 如果存在返回对应的回调函数,否则返回nullptr
*/
on_change_cb getListener(uint64_t key) {
RWMutexType::ReadLock lock(m_mutex);
auto it = m_cbs.find(key);
return it == m_cbs.end() ? nullptr : it->second;
}
/**
* @brief 清理所有的回调函数
*/
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}
private:
RWMutexType m_mutex;
T m_val;
//变更回调函数组, uint64_t key,要求唯一,一般可以用hash
std::map<uint64_t, on_change_cb> m_cbs;
};
3、管理类
class Config {
public:
typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
typedef RWMutex RWMutexType;
/**
* @brief 获取/创建对应参数名的配置参数
* @param[in] name 配置参数名称
* @param[in] default_value 参数默认值
* @param[in] description 参数描述
* @details 获取参数名为name的配置参数,如果存在直接返回
* 如果不存在,创建参数配置并用default_value赋值
* @return 返回对应的配置参数,如果参数名存在但是类型不匹配则返回nullptr
* @exception 如果参数名包含非法字符[^0-9a-z_.] 抛出异常 std::invalid_argument
*/
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = "") {
RWMutexType::WriteLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it != GetDatas().end()) {
auto tmp = std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
if(tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists";
return tmp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists but type not "
<< TypeToName<T>() << " real_type=" << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}
if(name.find_first_not_of("abcdefghikjlmnopqrstuvwxyz._012345678")
!= std::string::npos) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
throw std::invalid_argument(name);
}
typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
GetDatas()[name] = v;
return v;
}