SpringBoot 的使用的中的一個奇怪問題 - Jackson ObjectMapper的注入衝突
SpringBoot 的使用的中的一個奇怪問題:
Jackson中Mapper的注入衝突
0x1 問題
在工作中,使用的框架是SpringBoot,為了把一些物件轉換為web使用的json格式的資料,就常常需要一些框架來完成,關於Object轉換Json,常用的框架並不多,主要是這幾個:
- Jackson: 這個框架基本成為了Spring的標配。
- FastJson : 這個是國內的一個框架,出自阿里,在某些時候據說效率會比較高。
- Gson : 沒怎麼用過。
我現在使用的框架就是Jackson。
SpringBoot開發的系統,有一個application.properties
ObjectMapper
– 哦,這個就是Jackson
轉換Object
和Json
資料的東西。
但是,一個奇怪的問題就出現了,當這個新的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。