今天我们说一下配置文件的热加载,在项目开发的过程中,都会用到一些数据的文件(格式如:csv,json等)。使用时会把数据加载到对应的java对象中,同时为了避免重复加载数据,我们会在第一次使用时把数据加载到缓存中,再次使用直接读取缓存中的数据即可。那数据内容发生了变化,该怎么办呢?这里说一下为了避免数据内容被误修改,所有我们存储数据对象的属性都是final的。
格式如下:
public class ConfDemo {
/** a */
public final String a;
/** b */
public final int b;
.....
那热加载时,如何修改呢?一般两种方式,一是替换java对象,另一种就是使用反射修改。替换java对象的缺点就是有的时候我们会引用数据对象,那替换对象,不能修改这个引用对象的数据,所以最后我们选择了反射修改数据对象。
看下面例子:
public class Demo {
public final int a;
public Demo(int a) {
this.a = a;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Demo a = new Demo(1);
Class<?> clz = clz.getClass();
Filed f = clz.getDeclaredField("a");
f.setAccessible(true);
f.set(a, 3);
System.out.println(p.a);
}
}
这样我们完成了热替换。我们延伸一下反射相关的知识,看到这里大家是不是发现反射能够修改final的变量值的,其实这样的说法是不准确的,final的变量值是可以修改的,但是有个前提是:定义的时候没有初始化。如果定义的时候初始化了也是不能修改的。
即,如果变成如下形式,再用反射修改a的值,是不会成功的
public class Demo {
public final int a = 1;
}
那这是为什么呢?这是因为编译期间final类型的数据自动被优化了,所有用到该变量的地方自动替换成常量。
如果你就想初始化时设置值,还想使用反射修改这个值,还有一种方法来逃脱编译的优化,可以设置值为
public class Demo {
public final int a = null != null ? 1 : 1;
}
这样设置之后,也能进行反射修改。
那么我们继续延伸,是不是final修饰的都会被虚拟机优化呢?答案当然是否定的。基本数据类型加上String类型(直接双引号的字符串)都会被编译器优化。其他类型的final修饰的,还是可以被修改的。
说到这还有一个需要注意的点就是:上面修改值得方法对final类型的属性都能正常运行,但是对于static的也就是常量会报错。那我们怎么修改常量呢?
对于常量来讲也会按照final的优化进行,那如何修改呢?这里用到了Field的modifies,每一个标记都会有一个标识(可以通过查看Modifier的源码,来看每一个约束的对应标识),我们可以通过修改这个标识,然后进行对常量属性的修改,如下
public class Demo {
public static final int a = 1;
}
public class Test {
public static void main(String[] args) throws Exception {
Demo a = new Demo();
Class<?> clz = clz.getClass();
Filed f = clz.getDeclaredField("a");
Filed modifiers = Filed.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
// 修改属性标识
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
f.setAccessible(true);
f.set(a, 3);
System.out.println(p.a);
}
}
这样就能执行成功了,但是上面的例子是不能修改成功的。
配置的相关的热加载以及final属性反射相关的知识就说到这,有说的不对的地方,欢迎指正。