项目说明:spring cloud项目,nacos配置中心
项目场景:项目在nacos配置中心中配了较多配置信息,而且存在一些开关类型的参数,现在需要实现配置信息修改后实时生效的效果
(也就是,项目启动后,项目运行中每次使用配置信息的时候,都可以读取到最新的配置信息)
沟通后,采用了如下解决方法:
- 新增1个@Componenet+@RefreshScope注解的配置信息类MyProperties,该类中@Value引入所有需要动态加载的配置参数;
- 新增1个@Component+@RefreshScope注解的自定义上下文工具类SprintContextUtil;
- 调用的时候,在每个需要的类中,添加静态代码块,在该块中用自定义上下文的getBean方法获取MyProperties类,然后再读取Properties的不同属性(即每一项配置信息)出来使用
但是,不好用!!!
这种方法在我自己的项目A中调试后,一直报空指针异常NullPointerException
,为空的是自定义的上下文工具类SprintContextUtil中定义的静态变量context,报错位置见下方注释:
在新搭建的项目B中,没有报空指针,但是配置信息修改后,经过测试并没有实时生效
,也就是没有实现动态配置信息的读取
基本可以推断出与类和上下文加载顺序
有关,每次启动项目,都没进入setApplicationContext方法,然后直接到getBean的时候报了空指针,经过网上资料翻阅,没有找到比较类似的处理方法
通过测试后发现,配置信息没有立即生效,是因为static修饰的问题 (动态刷新配置不生效的参考资料)
参考上方自资料后,提取出来一段重要信息,见下图:
我的代码中是用了static修饰类属性,导致属性一直是用的类初始化时获取到的配置信息类对象实例,拿不到新的配置信息类,也就更无法从RefreshScope.get(x…)读取到的新bean的属性,下方是项目B的相关代码截图:
2个配置类:
配置信息类MyProperties
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@Data
@RefreshScope
public class MyProperties {
//接口开关
@Value("${test.switch}")
String testSwitch;
//接口开关
@Value("${test.cron}")
String testCron;
}
自定义的上下文工具类SpringContextUtil
import org.springframework.beans.BeansException;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
@RefreshScope
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext context = null;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if(SpringContextUtil.context == null){
SpringContextUtil.context = context;
}
}
public static ApplicationContext getApplicationContext(){
return context;
}
public static <T> T getBean(Class<T> clazz){
T test = getApplicationContext().getBean(clazz);
return test;
}
}
2个调用的类:
测试工具类TestUtils
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
public class TestUtils {
public static String TEST_SWITCH;
static{
MyProperties properties = SpringContextUtil.getBean(MyProperties.class);
TEST_SWITCH = properties.getTestSwitch();
}
public static String test(){
Map<String,String> map = new HashMap<>();
map.put("TEST_SWITCH",TEST_SWITCH);
return map.toString();
}
}
测试入口TestController
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
@GetMapping("/readConfig")
public String readConfig() {
return TestUtils.test();
}
}
- 不再使用上下文工具类SpringContextUtil;
- 工具类TestUtils中直接使用
@Autowired注入配置类
,然后在工具类的方法中,通过注入的配置类的实例对象
获取配置信息后进行使用;- 入口类TestController也通过
@Autowried注入工具类
,在入口方法中,通过注入的工具类的实例对象调用方法,将动态配置信息返回出去
调整后代码如下:
MyProperties.java(代码不变)
SpringContextUtil.java(代码不变,且不再使用)
测试工具类TestUtils.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
@RefreshScope
public class TestUtils {
// public static String TEST_SWITCH;
//
// static{
// MyProperties properties = SpringContextUtil.getBean(MyProperties.class);
// TEST_SWITCH = properties.getTestSwitch();
// }
//
// public static String test(){
// Map map = new HashMap<>();
// map.put("TEST_SWITCH",TEST_SWITCH);
// return map.toString();
// }
//上面全部注释,改用下面方式
@Autowired
MyProperties properties;
public String test(){
Map<String,String> map = new HashMap<>();
map.put("TEST_SWITCH",properties.getTestSwitch());
return map.toString();
}
}
测试入口类TestController.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
// @GetMapping("/readConfig")
// public String readConfig() {
// return TestUtils.test();
// }
//上面注释,改用下面方式
@Autowired
TestUtils testUtils;
@GetMapping("/readConfig2")
public String readConfig2() {
return testUtils.test();
}
}
nacos修改配置并完成部署(不重启项目):
注意:修改后必须成功部署
参考项目B方法取消了静态修饰,没有空指针问题了,也可以实现动态刷新nacos配置信息,这里就不截图了,与项目B解决方法一致
这里的动态刷新只是读取普通配置信息用于获取后赋值给变量使用,对于Cron表达式
,这里的方式无法实现动态刷新
,而且甚至配置信息修改部署后,如果不重启项目,可能会导致使用了配置信息中cron表达式的定时任务失效
,这个问题后续也会写文章记录下来,更新完后会把链接贴到这里(传送门:Cron表达式动态刷新实现)