天天看點

關于ApplicationContextAware接口的使用過程中出現的set方法沒執行的問題

關于ApplicationContextAware接口的使用過程中出現的set方法沒執行的問題

最近項目中遇到了一個非常有趣的問題,拿出來跟大家分享一下。

近期在跑單元測試的時候,報了一個applicationContext為null的異常

首先看一下代碼:這是我的測試類:

關于ApplicationContextAware接口的使用過程中出現的set方法沒執行的問題

這個類裡面調用了一個工具類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中手動配置:

關于ApplicationContextAware接口的使用過程中出現的set方法沒執行的問題

2.我更改了類的注入方式,經過測試,居然就好使了,但是為什麼呢?包掃描注解和xml配置bean原理是一樣的,為什麼注解就不好使呢?

3.後來我又在網上找了很久,有的人說如果手動配置了bean但是還要懶加載配置成false才好用:

如果我去掉:lazy-init=“false”

經過測試,依然是空指針的問題。難道這個和懶加載有關系?

那注解方式注入是懶加載嗎?

4.其實注解預設情況下不是懶加載的。他會在spring容器初始化的時候,根據配置的包掃描路徑,自動的給包下加了注解的對象注入到spring容器中。跟xml的配置加載是一緻的。那麼一定是某一個地方促使了DictConfigUtil的加載順序變了。

(這裡我猜想過:如果真的是懶加載,那麼我在調用這個類的時候,就會建立這個對象了,依然還是會觸發setApplicationContext方法執行的,後來我才知道,我調用的是這個類的靜态方法,根本不會去執行個體化這個類!)

5.後來在大神的幫助下我終于發現這個配置:

關于ApplicationContextAware接口的使用過程中出現的set方法沒執行的問題

我的天,這裡配置了一個全局的懶加載,這才導緻了注解方式明明不是懶加載,但是由于這個全局的配置優先級高,導緻了DictConfigUtil這個類的懶加載,由于我們調用這個類的時候,是直接調用這個類的靜态方法,是以,根本不會去初始化這個類,就不會觸發ApplicationContextAware執行setApplicationContext方法,這就解釋了為什麼會出現applicationContext為空的問題了!完美~

由于我的項目比較複雜,其實真實項目啟動在用這個代碼的時候,并沒有出現異常,我一直以為是spring單元測試有什麼特殊的地方導緻了這個ApplicationContextAware不執行set方法。。一度陷入單元測試的坑中,後來解決了這個問題,我去看正式的子產品xml配置這塊,就差在了這個額、全局的懶加載。不過我在想,就算正式的環境配置了這個懶加載,也有可能不會出現空指針,原因很複雜,有可能我在執行這個代碼之前這個對象就被别處的代碼給初始化完成過了。

知識點:

Spring_懶加載與非懶加載

懶加載:對象使用的時候才去建立,節省資源,但是不利于提前發現錯誤。

非懶加載:容器啟動的時候立刻建立對象。消耗資源。利于提前發現錯誤。

當scope=“prototype” (多例)時,預設以懶加載的方式産生對象。

當scope=“singleton” (單例)時,預設以非懶加載的方式産生對象。

關于ApplicationContextAware,推薦一個文章,寫的挺好的:https://blog.csdn.net/mdq11111/article/details/79050280