SpringCloud+Nacos项目实现配置信息动态刷新-报错问题记录


一、项目场景

项目说明:spring cloud项目,nacos配置中心

项目场景:项目在nacos配置中心中配了较多配置信息,而且存在一些开关类型的参数,现在需要实现配置信息修改后实时生效的效果
(也就是,项目启动后,项目运行中每次使用配置信息的时候,都可以读取到最新的配置信息)


二、问题描述

沟通后,采用了如下解决方法:

  1. 新增1个@Componenet+@RefreshScope注解的配置信息类MyProperties,该类中@Value引入所有需要动态加载的配置参数;
  2. 新增1个@Component+@RefreshScope注解的自定义上下文工具类SprintContextUtil;
  3. 调用的时候,在每个需要的类中,添加静态代码块,在该块中用自定义上下文的getBean方法获取MyProperties类,然后再读取Properties的不同属性(即每一项配置信息)出来使用

但是,不好用!!!

项目A

这种方法在我自己的项目A中调试后,一直报空指针异常NullPointerException,为空的是自定义的上下文工具类SprintContextUtil中定义的静态变量context,报错位置见下方注释:

在这里插入图片描述

项目B

在新搭建的项目B中,没有报空指针,但是配置信息修改后,经过测试并没有实时生效,也就是没有实现动态配置信息的读取


三、原因分析

项目A

基本可以推断出与类和上下文加载顺序有关,每次启动项目,都没进入setApplicationContext方法,然后直接到getBean的时候报了空指针,经过网上资料翻阅,没有找到比较类似的处理方法

项目B

通过测试后发现,配置信息没有立即生效,是因为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();
    }
}


四、解决方案

  1. 不再使用上下文工具类SpringContextUtil;
  2. 工具类TestUtils中直接使用@Autowired注入配置类,然后在工具类的方法中,通过注入的配置类的实例对象获取配置信息后进行使用;
  3. 入口类TestController也通过@Autowried注入工具类,在入口方法中,通过注入的工具类的实例对象调用方法,将动态配置信息返回出去

项目B

调整后代码如下:

2个配置类调整

MyProperties.java(代码不变)
SpringContextUtil.java(代码不变,且不再使用)

2个调用类调整

测试工具类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<String,String> 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改配置之前:
在这里插入图片描述

改配置之前测试:
在这里插入图片描述

nacos修改配置并完成部署(不重启项目):
注意:修改后必须成功部署
在这里插入图片描述

修改配置后,直接重新postman请求:
在这里插入图片描述

项目A

参考项目B方法取消了静态修饰,没有空指针问题了,也可以实现动态刷新nacos配置信息,这里就不截图了,与项目B解决方法一致


五、补充说明

这里的动态刷新只是读取普通配置信息用于获取后赋值给变量使用,对于Cron表达式,这里的方式无法实现动态刷新,而且甚至配置信息修改部署后,如果不重启项目,可能会导致使用了配置信息中cron表达式的定时任务失效,这个问题后续也会写文章记录下来,更新完后会把链接贴到这里(传送门:Cron表达式动态刷新实现)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迟到_啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值