1. 程式人生 > >SpringBoot 的使用的中的一個奇怪問題 - Jackson ObjectMapper的注入衝突

SpringBoot 的使用的中的一個奇怪問題 - Jackson ObjectMapper的注入衝突

SpringBoot 的使用的中的一個奇怪問題:

Jackson中Mapper的注入衝突


0x1 問題

在工作中,使用的框架是SpringBoot,為了把一些物件轉換為web使用的json格式的資料,就常常需要一些框架來完成,關於Object轉換Json,常用的框架並不多,主要是這幾個:

  • Jackson: 這個框架基本成為了Spring的標配。
  • FastJson : 這個是國內的一個框架,出自阿里,在某些時候據說效率會比較高。
  • Gson : 沒怎麼用過。

我現在使用的框架就是Jackson。

SpringBoot開發的系統,有一個application.properties

檔案,他可以配置很多關於Spring方方面面的基礎設定,其中就有這Jackson。由於某種特定的需求,我需要在原有的系統中新增一個新的功能,而這個功能的實現,需要一個自己的ObjectMapper – 哦,這個就是Jackson轉換ObjectJson資料的東西。

但是,一個奇怪的問題就出現了,當這個新的ObjectMapper作為Bean出現之後,原本SpingBoot中對於ObjectMapper的配置全都失去了效果,後來向前輩請教,得知應該是新的Mapper覆蓋了原有的Mapper。

專案有一個WebConfig類,其中一個方法是這樣的:

    @Bean
    public
JsonHandler jsonHandler(ObjectMapper objectMapper) { JsonHandler jh = new JsonHandler(); jh.setMapper(objectMapper); return jh; }

這個專案在一個WEBConfig類中配置了一個用於轉換Object到Json的類JsonHandler,它就需要一個ObjectMapper,在這個配置方法中打印出這個ObjectMapper的class,正是新配置的Mapper。

而且各個配置中都沒有看到對ObjectMapper的注入,那麼這個方法要求的ObjectMapper,在定義這個新的Mapper之前,他又是從哪裡來的呢?

0x2 新註解

我發現,在Spring的STS(Spring官方提供的一個Eclipse版本)中,按住Ctrl的時候,可以點開application.properties配置,看到對應的原始碼,而Jackson的配置,是在spring-boot-autoconfigure這個包裡面的,名字是JacksonProperties,而同一個包下,就有JacksonAutoConfiguration

首先我注意到的就是這個:

@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration 

很明顯,這是兩個註解,不過在Spring的使用中好像還沒有見過他們。
而在這後面不遠,就可以看到:

@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
	return builder.createXmlMapper(false).build();
}

看起來我找到那個ObjectMapper的Bean了。

0x3 這些註解的含義和使用
註解名稱 註解引數 含義 作用
ConditionalOnBean type,value bean的class物件,Bean的名字 僅當Bean存在的時候,此註解修飾的內容才會起到作用
ConditionalOnMissingBean type,value bean的class物件,Bean的名字 僅當Bean不存在的時候,此註解修飾的內容才會起作用
ConditionalOnClass name,value 類名,類的class物件 僅當class存在的時候,此註解修飾內容才會起效
ConditionalOnMissingClass value 類名 僅當class不存在的時候,此註解修飾的內容才會起效
ConditionalOnJava range,value 描述java版本的列舉,定義在此註解中。 僅當java版本符合的時候才會起效
Conditional value Condition介面的實現類 根據指定類判別是否讓修飾的內容起效
註解名稱 作用
Primary 優先提供此註解修飾的Bean
0x4 那麼,問題就解決了

從上面的JacksonAutoConfiguration中就可以知道,預設的ObjectMapper,是要在沒有其他ObjectMapper的Bean出現的時候才會出現的,所以要解決這個問題,做法也很簡單,將新的ObjectMapper新增@ConditionalOnBean這個註解,讓他在預設的ObjectMapper出現之後才能被註冊,這樣預設的那個ObjectMapper就不會自動消失了,而且由於預設的ObjectMapper帶有@Primary,所以會優先使用,到此為止,奇怪的Bean衝突問題就不存在了。

0x5 還是有一個後續

通過上面的操作後,可以發現雖然預設的ObjectMapper不會消失,但是CrudMapper卻消失了,真是讓人頭大,,,

接下來我取消了@ConditionalOnBean註解,改用@DependsOn來控制Bean的初始化順序,讓他能夠在jsonHandler載入之後在進行初始化,因為這個時候預設的ObjectMapper應該已經載入了,但是呢,想法是美好的,現實是殘酷的:

Description:
There is a circular dependency between 6 beans in the application context:

好吧,出現了迴圈引用,應該是由於新的ObjectMapper是ObjectMapper的子類,而預設的ObjectMapper一旦發現有其他ObjectMapper的Bean,就不會註冊,因此就導致了其他需要預設ObjectMapper的Bean對新的ObjectMapper出現了引用,這就出來了迴圈引用的問題。

我最終是這樣做的,使用泛型作為返回型別,抹除本來的型別簽名,這樣就會導致config中不存在ObjectMapper的Bean定義,從而使得Jackson預設的那個可以得到載入,然後讓他DependOn那個jsonHandler,確保它是在預設的ObjectMapepr之後才會出現。

註解名字 引數 含義
DependOn value(String[] Bean名字) 此Bean依賴於另外的Bean,必須在那些Bean之後進行初始化

另外,Java的Annotation配置中,Bean預設的id就是註解了@Bean的方法的名字,而@Autowired註解預設裝配是ByType而@Resource是ByName,這裡抹去了型別就是為了避免這種ByType的裝配,防止由此導致的迴圈依賴,不過抹除了型別就無法使用ByType裝配了,這裡我使用的時@Resource來注入這個新的ObjectMapepr。