本文是基于JFinal编写的一种读取配置文件的更加高效的方案。如果您没有使用JFInal,没必要阅读此篇文章,以免浪费您的时间。
使用过JFinal开发项目的朋友都知道,读取properties配置文件其实非常简单,JFinal已经提供了一个PropKit的工具类,使我们非常方便的读取到配置文件中的某个值。
在代码初始化的时候,首先初始化下Propkit,代码如下:
PropKit.use("jboot.properties");
然后在整个项目中,随便使用PropKit的get方法即可读取jboot.properties文件的任何内容,例如:
String wechatAppId = PropKit.get("wechat_app_id");
非常简洁,读取某个配置文件也就一行代码而已。
但是,但是...
在我们实际开发中,某个组件的配置,往往是由多个配置信息组成的,比如微信开发的相关配置,需要的信息可能如下:
wechat.debug = false
wechat.appid = xxx
wechat.appsecret = xxx
wechat.token = xxx
wechat.partner = xxx
wechat.paternerKey = xxx
wechat.cert = xxx
wechat.templateId = xxx
那么,如果我们在需要读取这些信息的时候,比如在拦截器里,我们可能需要编写如下的代码:
String wechatAppId = PropKit.get("wechat.id");
String wechatAppSecret= PropKit.get("wechat.appsecret");
String wechatToken = PropKit.get("wechat.token");
String wechatPartner = PropKit.get("wechat.partner");
String wechatPartnerKey = PropKit.get("wechat.partnerKey");
String wechatCert = PropKit.get("wechat.cert");
String wechatTemplateId = PropKit.get("wechat.templateId");
如果再多个地方有这样的需求,那么通常有几个方法:
1、复制这部分代码,到需要的地方。
2、把这部分的代码提取到一个独立的类,里面有getAppId(),getAppSercret()等方法,然后调用。
3、编写一个model类,然后在这个类进行实例化的时候,填充属性。
在项目中除了微信配置以外,还有非常多的各种配置,比如数据库配置信息、redis配置信息,缓存配置信息,rpc配置信息等等等等。无论您使用3中方法中的任何一种,都不是高效简洁的。
那么,是否可以有更好的办法呢?当然有。
方法如下:
给每中配置编写对于的一个model,然后通过一个配置文件直接读取到这个model。
例如:我们为微信的配置编写了一个WechatConfig类,代码如下:
public class WechatConfig{
private String wechatAppId;
private String wechatAppSecret;
private String wechatToken;
private String partner;
private String partnerKey;
private String cert;
private String templateId;
// getter setter 略
}
想获取这个配置信息,只需要通过一个配置文件即可。
WechatConfig config = MyPropUtils.getConfig(WechatConfig.class);
重点来了,如果编写MyPropUtils这个工具类。
先说思路:
1、读取到properties的所有配置信息。
2、反射读取到WechatConfig的字段。
3、把读取到的properties内容赋值到对应的字段去。
看起来也非常简单,直接编写代码。
public static <T> T getConfig(String propFile, Class<T> clazz) {
//不管三七二十一,先初始化这个类。
Object obj = ClassNewer.newInstance(clazz);
//读取到Prop信息
Prop prop = PropKit.use(propFile);
//找到这个类的所有set方法,model类默认有get和set的
List<Method> setMethods = new ArrayList<>();
Method[] methods = obj.getClass().getMethods();
if (ArrayUtils.isNotEmpty(methods)) {
for (Method m : methods) {
if (m.getName().startsWith("set")
&& m.getName().length() > 3
&& m.getParameterCount() == 1) {
setMethods.add(m);
}
}
}
//遍历所有set方法
for (Method method : setMethods) {
try {
// 得到这个set方法对应的key以及value值
String key = StrKit.firstCharToLowerCase(method.getName().substring(3));
String value = prop.get(key);
if (StringUtils.isNotBlank(value)) {
//如果这个value值不为空,把value值转化为该set方法需要的数据类型,
//可能是boolean,int,datetime等类型
Object val = convert(method.getParameterTypes()[0], value);
//执行这个方法并进行赋值
method.invoke(obj, val);
}
} catch (Throwable ex) {
log.error(ex.toString(), ex);
}
}
//把这个object返回
return (T) obj;
}
嗯,看起来还不错,但是还是有些问题,这个有些局限性,很多时候在一个配置文件里面可能需要有多个名称一样的配置,比如redis的可能有一个叫password的配置,数据库可能也需要一个叫password的配置,因此,在配置文件里面我们通常会给某个组件添加上前缀,比如redis的相关配置是 redis.password,redis.host这样的,而数据库可能是 mysql.password,mysql.user 等等这样的配置,因此我们需要给这个工具类添加下 可以读取前缀的功能。
还有另一个缺点是,每次调用这个getConfig方法,都是重新实例化一个新的实例,配置信息在整个项目中都很少发生变化,为了更加高效,我们可以把这个getConfig读取到的model都是单利的,而不是每次都是一个新的实例。
因此,我们可以优化如下:
private static ConcurrentHashMap<String, Object> configs = new ConcurrentHashMap<>();
public static <T> T getConfig(String prefix, String propFile, Class<T> clazz) {
//检查前缀
if (StringUtils.isBlank(prefix)) {
throw new JbootException("prefix must not be null");
}
//先尝试从map中获取内容
Object obj = configs.get(prefix);
if (obj != null) {
return (T) obj;
}
obj = ClassNewer.newInstance(clazz);
Prop prop = PropKit.use(propFile);
List<Method> setMethods = new ArrayList<>();
Method[] methods = obj.getClass().getMethods();
if (ArrayUtils.isNotEmpty(methods)) {
for (Method m : methods) {
if (m.getName().startsWith("set")
&& m.getName().length() > 3
&& m.getParameterCount() == 1) {
setMethods.add(m);
}
}
}
for (Method method : setMethods) {
try {
String key = StrKit.firstCharToLowerCase(method.getName().substring(3));
//添加上前缀的key
key = prefix.trim() + "." + key;
String value = prop.get(key);
if (StringUtils.isNotBlank(value)) {
Object val = convert(method.getParameterTypes()[0], value);
method.invoke(obj, val);
}
} catch (Throwable ex) {
log.error(ex.toString(), ex);
}
}
//设置到map里去
configs.put(prefix, obj);
return (T) obj;
}
代码写到这里,已经很不错了,但是还有几个小问题:
1、调用这个方法需要穿3个参数,太麻烦了。
2、通常在我们整个项目中,一般就只有几个配置文件,不会太多,所以没有必要每次都传配置文件的名字。
我们继续优化下,处理办法是添加几个重载的方法。
private static ConcurrentHashMap<String, Object> configs = new ConcurrentHashMap<>();
private static final String defalutConfigFile = "jboot.properties";
public static <T> T get(Class<T> clazz) {
PropertieConfig propertieConfig = clazz.getAnnotation(PropertieConfig.class);
if (propertieConfig == null) {
throw new JbootException("PropertieConfig annotation must
not be null in class : " + clazz);
}
return getConfig(propertieConfig.prefix(), propertieConfig.file(), clazz);
}
public static <T> T getConfig(String prefix, Class<T> clazz) {
return getConfig(prefix,defalutConfigFile,clazz);
}
public static <T> T getConfig(String prefix, String propFile, Class<T> clazz) {
//检查前缀
if (StringUtils.isBlank(prefix)) {
throw new JbootException("prefix must not be null");
}
//先尝试从map中获取内容
Object obj = configs.get(prefix);
if (obj != null) {
return (T) obj;
}
obj = ClassNewer.newInstance(clazz);
Prop prop = PropKit.use(propFile);
List<Method> setMethods = new ArrayList<>();
Method[] methods = obj.getClass().getMethods();
if (ArrayUtils.isNotEmpty(methods)) {
for (Method m : methods) {
if (m.getName().startsWith("set")
&& m.getName().length() > 3
&& m.getParameterCount() == 1) {
setMethods.add(m);
}
}
}
for (Method method : setMethods) {
try {
String key = StrKit.firstCharToLowerCase(method.getName().substring(3));
//添加上前缀的key
key = prefix.trim() + "." + key;
String value = prop.get(key);
if (StringUtils.isNotBlank(value)) {
Object val = convert(method.getParameterTypes()[0], value);
method.invoke(obj, val);
}
} catch (Throwable ex) {
log.error(ex.toString(), ex);
}
}
//设置到map里去
configs.put(prefix, obj);
return (T) obj;
}
OK,代码编写完了,如何使用呢?
配置内容如下:
jboot.myconfig.name=aaa
jboot.myconfig.passowrd=bbb
jboot.myconfig.age=10
编写model类:
@PropertieConfig(prefix="jboot.myconfig")
public class MyConfigModel{
private String name;
private String password;
private int age;
//getter setter 略
}
读取 model 内容:
MyConfigModel config = MyPropUtils.getConfig(MyConfigModel.class);
怎么样?是不是跟简单,有更多时间陪女朋友了?
具体代码在:https://git.oschina.net/fuhai/jboot/blob/master/src/main/java/io/jboot/config/JbootProperties.java ,请执行参考。
我的个人微信公众号,经常分享一些技术心得和心里路程,希望也能认识你。