关于ApplicationContextAware接口的使用过程中出现的set方法没执行的问题
最近项目中遇到了一个非常有趣的问题,拿出来跟大家分享一下。
近期在跑单元测试的时候,报了一个applicationContext为null的异常
首先看一下代码:这是我的测试类:这个类里面调用了一个工具类DictConfigUtil .getConfig(propertyName)获取redis的对象,这个工具类继承了ApplicationContextAware接口。
@Service(“dictConfigUtil”)
public class DictConfigUtil implements ApplicationContextAware {
private static Logger log = Logger.getLogger(DictConfigUtil.class);
private static ApplicationContext applicationContext;
public static final Set<String> TYPES = new LinkedHashSet<String>();
private static Map<String, SysProperty> inmemoryConfigConext = new HashMap<String, SysProperty>();
public DictConfigUtil() {
}
static {
TYPES.add("String");
TYPES.add("Number");
TYPES.add("Boolean");
TYPES.add("Map");
TYPES.add("array");
TYPES.add("List");
TYPES.add("Set");
System.out.println("调用了static");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
System.out.println("调用了set方法,当前ax");
DictConfigUtil.setStaticApplicationContext(context);
}
public static void setStaticApplicationContext(ApplicationContext ax) {
applicationContext = ax;
}
/**
* 可以在测试时使用该方法将参数配置缓存至内存中, 这时就不再需要对ConfigUtil及其关联的applicationContext对象做Mock。
* 将属性缓存后,就可以通过getXXConfig得到该属性的配置。 该方法主要用于简化单元测试。
*
* @param property
*/
public static void cachePropertyInMemory(SysProperty property) {
inmemoryConfigConext.put(property.getName(), property);
}
/**
* 根据参数的名称读取参数配置
*
* @param propertyName
* @return
*/
public static Object getConfig(String propertyName) {
Cluster jimClient = null;
try {
jimClient = (Cluster) applicationContext.getBean(Cluster.class);
} catch (Exception e) {
log.error("jimdb连接获取失败!!!",e);
SysPropertyService service = applicationContext.getBean(SysPropertyService.class);
SysProperty property = service.getPropertyByName(propertyName);
if(property != null){
return getValue(property.getType(), property.getValue());
}else{
return null;
}
}
String value = jimClient.hGet(Constant.SYS_PROPERTY_JIM_OBJECT_NAME, propertyName);
String type = jimClient.hGet(Constant.SYS_PROPERTY_JIM_OBJECT_NAME, propertyName + Constant.JIM_PROPERTY_TYPE);
if(value != null && type != null){
return getValue(type, value);
}else{
SysPropertyService service = applicationContext.getBean(SysPropertyService.class);
SysProperty property = service.getPropertyByName(propertyName);
if(property != null){
jimClient.hSet(Constant.SYS_PROPERTY_JIM_OBJECT_NAME, propertyName, property.getValue());
jimClient.hSet(Constant.SYS_PROPERTY_JIM_OBJECT_NAME, propertyName + Constant.JIM_PROPERTY_TYPE, property.getType());
return getValue(property.getType(), property.getValue());
}else{
return null;
}
}
}
按照ApplicationContextAware的用法,他会在初始化这个实现它的类:DictConfigUtil的时候,自动的调用SetApplicationContext方法,将上下文对象赋值给DictConfigUtil的applicationContext,但是我们从applicationContext.getBean(Cluster.class);获取redis的对象,报出了一个空指针异常,applicationContext=null,这是为什么呢?
对于这种情况,我是第一想法就是验证这个set方法有没有执行,然后我就在set方法里面打印了一句话,再次测试,发现根本没有打印,但是静态代码块执行了,那这个类到底有没有初始化呢。。
接下来我还验证了自己的想法:
1.首先我先检查了一下这个DictConfigUtil类,看看这个类有没有正常的初始化,在这个类上是有一个注解:@Service并且在配置文件中:
<context:component-scan base-package=“com.xstore.pms” />
包路径完全正确,没道理不加载呀。然后我暂且认为类初始化完成~
接下来我就陷入了类初始化完成但是set方法不执行的执着中~~
后来google了很多关于ApplicationContextAware的set方法不执行的问题解决,好多说要在xml中手动配置:
2.我更改了类的注入方式,经过测试,居然就好使了,但是为什么呢?包扫描注解和xml配置bean原理是一样的,为什么注解就不好使呢?
3.后来我又在网上找了很久,有的人说如果手动配置了bean但是还要懒加载配置成false才好用:
如果我去掉:lazy-init=“false”
经过测试,依然是空指针的问题。难道这个和懒加载有关系?
那注解方式注入是懒加载吗?
4.其实注解默认情况下不是懒加载的。他会在spring容器初始化的时候,根据配置的包扫描路径,自动的给包下加了注解的对象注入到spring容器中。跟xml的配置加载是一致的。那么一定是某一个地方促使了DictConfigUtil的加载顺序变了。
(这里我猜想过:如果真的是懒加载,那么我在调用这个类的时候,就会创建这个对象了,依然还是会触发setApplicationContext方法执行的,后来我才知道,我调用的是这个类的静态方法,根本不会去实例化这个类!)
5.后来在大神的帮助下我终于发现这个配置:
我的天,这里配置了一个全局的懒加载,这才导致了注解方式明明不是懒加载,但是由于这个全局的配置优先级高,导致了DictConfigUtil这个类的懒加载,由于我们调用这个类的时候,是直接调用这个类的静态方法,所以,根本不会去初始化这个类,就不会触发ApplicationContextAware执行setApplicationContext方法,这就解释了为什么会出现applicationContext为空的问题了!完美~
由于我的项目比较复杂,其实真实项目启动在用这个代码的时候,并没有出现异常,我一直以为是spring单元测试有什么特殊的地方导致了这个ApplicationContextAware不执行set方法。。一度陷入单元测试的坑中,后来解决了这个问题,我去看正式的模块xml配置这块,就差在了这个额、全局的懒加载。不过我在想,就算正式的环境配置了这个懒加载,也有可能不会出现空指针,原因很复杂,有可能我在执行这个代码之前这个对象就被别处的代码给初始化完成过了。
知识点:
Spring_懒加载与非懒加载
懒加载:对象使用的时候才去创建,节省资源,但是不利于提前发现错误。
非懒加载:容器启动的时候立刻创建对象。消耗资源。利于提前发现错误。
当scope=“prototype” (多例)时,默认以懒加载的方式产生对象。
当scope=“singleton” (单例)时,默认以非懒加载的方式产生对象。
关于ApplicationContextAware,推荐一个文章,写的挺好的:https://blog.csdn.net/mdq11111/article/details/79050280