java读取properties值-Spring中文件内容读入的设计模式较好呢?
前言:
大家都知道Spring中有一个@Value注解,可以将配置文件中的配置注入到类中的变量。在需要使用自定义配置的情况下,这个功能显得很重要。
本文主要实现了一个类似@Value注解,可以将配置文件中的配置项注入到相应的注解所对应的变量中。目前1.0版本相对简陋——称之为demo更为准确,可以读取yaml格式的配置文件,文件名需要固定为application.yml;同时因为没有spring的bean容器的加持,基于反射的原理,目前智能实现将注解加在静态变量上。本项目将持续优化,相关文章也会持续更新,后期考虑首先加入读取多种文件格式的功能。
最后说一下,为什么Sunny要开发这个SDK。Spring中有这个功能,相对比较方便,但是事实上,如果项目用不到Spring,只是为了读取配置而加入堆Spring的依赖显得有些重。而事实上,在很多场景中,需要用到这个功能,目前所知的有部分内容JDK已经包含了一定的支持,比如Properites类,但是缺少一个完整的解决方案,以及对注解的支持。
正式开始
要完成一个类似的功能,首先要考虑几个方面。本文也将从以下几个方面来详细说说。
1. 读取配置内容
读取配置项这块应该算是相对较为简单的一部分内容了。其实真正的实现就是一个ClassPath中文件内容读入的过程。不过需要注意的一点是,这里如果涉及多种文件格式的读入,需要用到一点设计模式的内容,大家觉得应该用什么设计模式较好呢?可以留言哦。
此处,我们就简单考虑一下yaml格式的文件读入。我们用到了snakeyaml的Yaml类,这个类可以帮助我们解析yaml格式,也避免了我们在这部分重复造轮子——今后提供对properties文件的支持,也将采用Properties类进行读入。
核心的读入代码为:
public static Object loadProperties(String path) throws IOException {
Yaml yaml = new Yaml();
Object o = yaml.load(readFile(path));
return o;
}
非常简单,这里入参就是一个path作为配置文件在classpath下的文件路径,readFile则是Sunny实现的一个从classpath中读取文件的方法,会返回读取文件的内容转换成String。此处,loadProperties将返回一个Object对象,对象中其实包含了一个配置树——具体可以看成一个嵌套Map。
2. 自定义注解
自定义注解也是一个相对较为简单的部分,话不多说,直接上代码:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfPath {
public String value() default "";
}
熟悉自定义注解的童鞋,读起来应该没什么难度。Sunny在这里简单说一下,上面三个注解是元注解,通常定义自定义注解用的。@Target注解说明,这个自定义注解是用来修饰什么的,这里就是修饰类成员变量的。@Retention可以看成这个自定义注解的生存周期,此处就是运行时。@Documented表示这个自定义注解会被JavaDoc记录。
3. 注解执行器
注解执行器是整个项目实现中最难的一部分。他主要完成,对整个包的扫描,找出有相应的注解修饰的元素,并从读取的配置项中找到相对应的匹配内容注入。
这部分的核心代码如下:
private static void putInConf(Class clazz){
Field[] fields = clazz.getDeclaredFields();
Object oo = null;
try {
oo = LocalPropertiesUtils.loadProperties("application.yml");
} catch (IOException e) {
e.printStackTrace();
}
for (Field field : fields) {
if(field.isAnnotationPresent(ConfPath.class)){
//static检查
if((field.getModifiers()&8) == 0){
throw new RuntimeException("配置项必须为static变量");
}
ConfPath confPath = field.getAnnotation(ConfPath.class);
Object o = oo;
String[] props = confPath.value().split("\\.");
int ind = 0;
while (true){
if(ind < props.length && o instanceof Map){
o = ((Map) o).get(props[ind]);
}else {
break;
}
ind ++;
}
try {
field.setAccessible(true);
field.set(field,String.valueOf(o));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
这个方法实现了获得一个类,将类中的成员变量遍历,找出标记了ConfValue注解的成员变量,同时检查是否为静态变量——因为无法获得实例java读取properties值,因此只能通过反射给类变量赋值。随后便是一个while循环,用于找出具体的配置项的值,之前也说过了,loadProperties方法返回的是一个嵌套Map,因此类似“server.port”之类的配置需要多次循环找到配置的值,通常配置是几层,循环需要几次。每一层,都将获得的对象强制转换成Map,并获得他相对应的key的value。最后则是通过反射赋值。
细心的童鞋,可能发现了,还有一部分内容这里没有提到,那就是包扫描。对整个包进行扫描java读取properties值,获得类名,遍历所有类,然后调用上面提到的putInConf方法将配置内容注入修饰了注解的类变量。这块内容,主要还是通过class文件扫描来完成,通过文件扫描来获得具体的类名集合,并循环调用putInConf方法。
后记
至此,1.0版本或者说demo就完成了。虽然相对非常简单,但是核心基本是这些内容,后期主要考虑几个方面优化。首先增加功能,尽量可以做到@Value注解的全部功能,包括但不限于对xml和properties格式的支持,以及对application-{env}.XXX的支持。其次考虑是否可以做到实例变量也能支持,需要找到一个较好的用户体验的方案。最后,考虑对整体设计进行优化,包括代码重构。