2017-05-26 51 views
3

我有一個公開JSON REST API的spring-boot應用程序。爲了將對象映射到json,它使用由spring-boot配置的內置jackson ObjectMapper。如何聲明另一個Jackson ObjectMapper而不影響原始bean的'客戶端'?

現在我需要從yaml文件中讀取一些數據,並且我發現使用Jackson的一個簡單方法是使用Jackson - 爲此我需要聲明一個不同的ObjectMapper將yaml轉換爲對象。我宣佈這個新的映射豆具有特定名稱能在我的業務處理從YAML文件中讀取到其注射:

@Bean(YAML_OBJECT_MAPPER_BEAN_ID) 
public ObjectMapper yamlObjectMapper() { 
    return new ObjectMapper(new YAMLFactory()); 
} 

但我需要一種方法來告訴所有的原始的其他「客戶」 json ObjectMapper繼續使用這個bean。所以基本上我需要在原始bean上使用@Primary註解。有沒有一種方法可以實現這一點,而無需在我自己的配置中重新聲明原始的ObjectMapper(我必須通過spring-boot代碼來查找並複製其配置)?

我發現的一個解決方案是爲ObjectMapper聲明一個FactoryBean,並使其返回已聲明的bean,如this answer中所建議的。我發現,我的原豆被稱爲「_halObjectMapper」調試,所以我的FactoryBean將搜索這個bean並返回:

public class ObjectMapperFactory implements FactoryBean<ObjectMapper> { 

    ListableBeanFactory beanFactory; 

    public ObjectMapper getObject() { 
     return beanFactory.getBean("_halObjectMapper", ObjectMapper.class); 
    } 
    ... 
} 

然後在我的配置I類聲明爲@Primary豆,以確保它的用於自動裝配的首選:

@Primary 
@Bean 
public ObjectMapperFactory objectMapperFactory(ListableBeanFactory beanFactory) { 
    return new ObjectMapperFactory(beanFactory); 
} 

不過,我不是很滿意這個解決方案100%,因爲它依賴於它是不是我的控制下的bean的名字,也似乎是一個黑客。有更清潔的解決方案嗎?

謝謝!

+0

我也嘗試使用'YAMLMapper'(它擴展了'ObjectMapper')作爲我的自定義bean類型,但spring仍然會抱怨它找到了兩個'ObjectMapper'類型的bean。我沒有想到這一點,儘管在某種程度上它是合理的。所以今天我又學到了一些新東西。這意味着如果我自動編寫一個類型爲「List 」的字段,我將獲得上下文中所有可用的豆... – Timi

+0

檢查我的答案更新。 –

回答

1

您可以定義兩個ObjectMapper豆,並宣佈一個爲主要,如:

@Bean("Your_id") 
public ObjectMapper yamlObjectMapper() { 
    return new ObjectMapper(new YAMLFactory()); 
} 

@Bean 
@Primary 
public ObjectMapper objectMapper() { 
    return new ObjectMapper(); 
} 

一旦這樣做,你可以用@Qualifier註釋使用objectmapper豆,如:

@Autowired 
@Qualifier("Your_id") 
private ObjectMapper yamlMapper; 

更新

你可以動態地將你的ObjectMapper添加到Spring的運行時的bean工廠,例如:

@Configuration 
public class ObjectMapperConfig { 

    @Autowired 
    private ConfigurableApplicationContext context; 

    @PostConstruct 
    private void init(){ 
     BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ObjectMapper.class); 
     builder.addConstructorArgValue(new JsonFactory()); 
     DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory(); 
     factory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition()); 
     Map<String, ObjectMapper> beans = context.getBeansOfType(ObjectMapper.class); 
     beans.entrySet().forEach(System.out::println); 
    } 
} 

上面的代碼添加一個新的豆到context無需改變現有的bean(sysout打印兩種豆在init方法的結束)。然後,您可以使用「yamlMapper」作爲限定符在任何地方自動裝載它。

更新2(從提問作者):

該解決方案在「更新」工作建議,這裏是一個簡化版本:

@Autowired 
private DefaultListableBeanFactory beanFactory; 

@PostConstruct 
private void init(){ 
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(YAMLMapper.class); 
    beanFactory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition()); 
} 
+0

這是我嘗試的第一件事,但'新的ObjectMapper()'沒有像spring-boot定義的那樣配置,所以我得到了很多失敗的測試。這就是爲什麼我想繼續使用完全相同的bean,而不是影響當前的API。 – Timi

+0

這與我之前創建的答案完全相同。你爲什麼這樣做? – luboskrnac

+0

@luboskrnac你和我的答案之間的差異不到2分鐘。你認爲我讀了你的答案,改寫了它,並在那段時間輸入了我的答案和解釋嗎? –

0

我相信定義MVC層明確的主要對象映射應該以這種方式工作:

@Primary 
@Bean 
public ObjectMapper objectMapper() { 
    return Jackson2ObjectMapperBuilder.json().build(); 
} 

所有bean是通過自動裝配類型映射對象將使用上面的bean。您的Yaml邏輯可以通過YAML_OBJECT_MAPPER_BEAN_ID自動裝配。

+0

我試過了您的建議,但它與'新的ObjectMapper()' - 它不會重複使用原始映射器的彈簧引導配置,所以我再次遇到失敗的測試。 – Timi

+0

順便說一下,Darshan Mehta創建了一個剽竊我的方式不好的答案,因爲這會創建更好的以Spring默認值啓動的bean。 – luboskrnac

+0

你在測試中遇到什麼樣的異常? – luboskrnac

1

其他選項是包裝定製映射到自定義對象:

@Component 
public class YamlObjectMapper { 
    private final ObjectMapper objectMapper; 

    public YamlObjectMapper() { 
     objectMapper = new ObjectMapper(new YAMLFactory()); 
    } 

    public ObjectMapper getMapper() { 
     return objectMapper; 
    } 
} 

不幸的是這種方法需要調用getMapper你注入YamlObjectMapper後。

0

我才意識到,我並不需要使用一個FactoryBean,我也可以同樣聲明的bean作爲@Primary並使其返回原來的綠豆,就像這樣:

@Bean 
@Primary 
public ObjectMapper objectMapper(@Qualifier("_halObjectMapper") ObjectMapper objectMapper) { 
    return objectMapper; 
} 

釷會使配置稍微更清晰,但仍需要原始ObjectMapper的確切名稱。不過,我想我會留下這個解決方案。

相關問題