问题背景
最近在将配置中心从apollo迁移到Nacos,涉及的一些变量的获取需要修改,这里遇到的问题是static变量使用@Value注解获取,但这里会报空指针错误,如下:
@Value("${file.server.addr}")private static String serverAddr;
因为用到serverAddr
变量的地方在一个静态方法里面,改成非静态变量的话改动成本又比较大,所以考虑需要解决这个问题,尝试检查工具类是否交给了Spring管理,在类上添加@Component
或者@Configuration
注解,但这样也是不会生效的,原因就是静态成员变量在类加载时就初始化,而此时Nacos的值还未加载或获取到,它会优先于Spring Bean对象的初始化,所以无法注入进去。
解决方案:
1.通过set方法注入
private static String serverAddr;@Value("${file.server.addr}")public void setServerAddr(String sd){serverAddr = sd;}
2.通过@PostConstruct
private static String serverAddr;@Value("${file.server.addr}")private String sd;@PostConstructpublic void init(){serverAddr = sd;}
@PostConstruct
是Java自带的注解,通常用来执行一些初始化操作,被@PostConstruct
修饰的方法需要为非静态的void()
方法,会在构造函数执行执行之后执行。
3.通过实现InitializingBean接口
@Component
public class MyUtil implements InitializingBean {private static String serverAddr;@Value("${file.server.addr}")private String sd;@Overridepublic void afterPropertiesSet() throws Exception {serverAddr = sd;}
}
InitializingBean
接口是Spring的一个扩展接口,提供了Bean的初始化方法:afterPropertiesSet
,从名字上也可看出,方法的执行阶段是在bean的属性被设置值之后,常用来修改默认设置的属性。另外它与Bean中的init-method
属性对应的方法功能是一致的,同时它也是在init-method
方法前执行。
上面的一些执行时机如下:
Constructor -> @Autowired -> @PostConstruct->afterPropertiesSet
4.SpringContextUtil获取
1)Config.java
@Component
public class FileConfig{@Autowiredprivate ConfigurableApplicationContext configurableApplicationContext;@Value("${file.server.addr:}")private String serverAddr;public String getServerAddr() {return serverAddr =configurableApplicationContext.getEnvironment().getProperty("file.server.addr");}
}
2) SpringContextUtil.java
@Component
public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;/*** 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException {SpringContextUtil.applicationContext = applicationContext;}/*** 取得存储在静态变量中的ApplicationContext.*/public static ApplicationContext getContext() {checkApplicationContext();return applicationContext;}public static Object getBean(String name) throws BeansException{return applicationContext.getBean(name);}private static void checkApplicationContext() {Assert.notNull(applicationContext,"applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");}
}
3)static调用类
public class TestService {public static void initTest() {FileConfig config = (FileConfig) SpringContextUtil.getBean("fileConfig");...}
}
原来的静态方法里面获取静态变量,可以从Spring容器中的Bean对象获取,变量也封装到了FileConfig类里面,静态方法里面的Bean对象可以通过SpringContextUtil
工具类来获取。
注意:
1.这里@Value("${file.server.addr}")
里面的变量值要确认存在于Nacos配置文件当中并确保格式正确;
2.SpringContextUtil
如果里面applicationContext
有空指针问题,请检查SpringContextUtil
有没有被Spring扫描到;
总结
static方法修饰的静态变量从配置文件中获取,用到的地方还是挺多的,比如在一些工具类当中,通常这些工具类里面的变量是全局的,在很多地方都有调用。这里1/2/3方案都是先将Spring对象中的变量加载完成后,再主动将值赋值给静态变量实现的,因为只加载一次,所以当Nacos配置文件中值更新的时候,我们需要重启服务才能生效。