关于应用 JSON 作为配置文件,并不鲜见。小弟若干年前亦有这方面的尝试,详见《Java Web:JSON 作为配置文件,简单读写的方法》,那时不成熟的地方还很多,问题不少——这当然是今天回过头去看的。不过好在我坚持下来了,还是抱着 JSON 不放,打造今天的配置中心。
为什么要用 JSON 作配置文件使用呢?如果要简单使用一个配置文件,大多数人会立刻想到 Java 的 properties 文件,或者 win 下面的 *.ini
文件,它们都是键值对的形式来保存属性集。然而 properties 文件只支持单层结构,且类型只有字符串。于是笔者考虑另外一种格式作为配置文件,这样 JSON 作为一种通用、可扩展的文本存储结构进入了笔者思考的视野中——到底 JSON 能不能作为配置文件使用呢?通过本配置系统的尝试,应该会给大家带出答案的。
配置 JSON
所谓配置 JSON 就是普通的一个 JSON。我们约定 {}
开始,而不是数组 []
。
{
"site" : {
"titlePrefix" : "大华官网",
"keywords" : "大华•川式料理",
"description" : "大华•川式料理饮食有限公司于2015年成立,本公司目标致力打造中国新派川菜系列。炜爵爷川菜料理系列的精髓在于清、鲜、醇、浓、香、烫、酥、嫩,擅用麻辣。在服务出品环节上,团队以ISO9000为蓝本建立标准化餐饮体系,务求以崭新的姿态面向社会各界人仕,提供更优质的服务以及出品。炜爵爷宗旨:麻辣鲜香椒,美味有诀窍,靓油用一次,精品煮御赐。 ",
"footCopyright":"版权所有"
},
"dfd":{
"dfd":'fdsf',
"id": 888,
"dfdff":{
"dd":'fd'
}
},
"clientFullName":"大华•川式料理",
"clientShortName":"大华",
"isDebug": true,
"data" : {
"newsCatalog_Id" : 6,
"jobCatalog_Id" :7
}
}
假设我们规定配置文件命名是 site_config.json
,保存在 classpath 根目录下(一般是与 com 包同级的)。这样,通过 class.getClassLoader().getResource("").getPath() 就可以获取磁盘的绝对位置。当然,我们有相应的类封装好该逻辑,不用额外去写(当然保存位置不一样的话要另说)。这个类就是接下来要说的 ConfigService。
如何读取配置
ConfigService 负责 JSON 的序列化和反序列化工作——简单说就是读取 JSON 内容和保存 JSON,这样 JSON 才能变为 Java 可理解的对象。
读取配置之前必须将其加载,对应的方法是 ConfigService.load()/load(String jsonPath)(后者可指定配置文件路径而不是默认),不过这一般不需要我们手动调用,框架内会自动调用,具体做法是:增加一个 Web 监听器,于 ServletContextListener.contextInitialized() 内调用即可——当然这是框架封装好的,一般不需要我们关心,实际我们要懂得是怎么读取配置。这个问题我们分为 Java 中读取和表示层中读取。
Java 代码中读取配置
这个很简单,就是访问静态属性 ConfigService.config,所有的配置保存在这个 config 中。这个 config 是扩展 HashMap 的自定义对象也就是说它是一个 map。所谓读取方法就是 map.get(“XXX”) 的使用。
public class ConfigService {
/**
* 所有的配置保存在这个 congfig 中
*/
public static Config config;
...
}
Config 继承自 HashMap<String, Object>。
public class Config extends HashMap<String, Object>
HashMap 中的配置值是 Object 类型,所以还不能直接使用,需要强类型转换一下。
类型转换倒不是麻烦,真正麻烦的是每访问一层对象,就要 get 一下,故所以为了读取一个配置会出现读 n 层的麻烦。有鉴于此,我们提供了方便的一个“扁平化 map”的访问机制:只要输入字符串“aa.bb.cc” 即可直接返回配置值。
public class ConfigService {
/**
* 所有的配置保存在这个 congfig 中
*/
public static Config config;
/**
* 所有的配置保存在这个 congfig 中(扁平化处理过的)
*/
public static Map<String, Object> flatConfig;
...
}
扁平化读取 ConfigService.flatConfig 即可。
指定类型读取配置
前文提到读取到的配置内容要强制转换才能使用。这样就不太方便了,对此,我们采用指定类型的方法,免去强类型转换的步骤。当然这需要程序员明确配置值的类型为何,否则会抛出类型转换的异常。
转换方法有面向 Boolean、Int、String 这三种常见的类型。
/**
* 读取配置并转换其为特定类型。仅对扁平化后的配置有效,所以参数必须是扁平化的 aaa.bbb.ccc 格式。
*
* @param key
* 配置键值
* @return 配置内容
*/
public static boolean getValueAsBool(String key);
public static int getValueAsInt(String key);
public static String getValueAsString(String key);
需要注意的是,这三个方法仅对扁平化后的配置有效(即访问 flatConfig),所以参数必须是扁平化的 aaa.bbb.ccc 格式。
页面模版中读取配置
怎么把配置开放给页面读取使用呢?方法多种多样~
第一种方法是保存在 Servlet 上下文。首先在初始化 Servlet 时执行 ServletContext.setAttribute(“all_config”, ConfigService.config); 所有配置保存在 all_config,读取某个配置 getAttribute(“all_config”).get("")。当然不会直接在页面写 Java 语句,而是 EL 表达式读取,那样方便得多,如 ${all_config.site.keywords}。
<head>
<meta charset="utf-8" />
<meta name="keywords" content="${all_config.site.keywords}" />
<meta name="description" content="${all_config.site.description}" />
<meta name="author" content="Frank Chueng, frank@ajaxjs.com" />
有时候并不具备修改 ServletContext 初始化的条件,于是我们可以考虑第二种方法,就是页面实例化 ConfigService 对象,然后 EL 表达式就可以访问配置变量了。因为在页面中,所以建议采用标签的写法实例化。
<jsp:useBean id="ConfigService" class="com.ajaxjs.config.ConfigService" scope="request" />
<%-- 读取配置 --%>
${ConfigService.config.site.keywords}
读取配置的一个例子
关于通用配置中心的说明,容我先休息下,先说到这里。