如何在不影响原始 bean 的“客户”的情况下声明另一个 Jackson ObjectMapper?
Posted
技术标签:
【中文标题】如何在不影响原始 bean 的“客户”的情况下声明另一个 Jackson ObjectMapper?【英文标题】:How to declare another Jackson ObjectMapper without affecting 'clients' of the original bean? 【发布时间】:2017-10-26 23:02:34 【问题描述】:我有一个公开 json REST API 的 spring-boot 应用程序。为了将对象映射到 json,它使用 spring-boot 配置的内置 jackson ObjectMapper。
现在我需要从 yaml 文件中读取一些数据,我发现一个简单的方法是使用 Jackson - 为此我需要声明一个不同的 ObjectMapper 来将 yaml 转换为对象。我用一个特定的名称声明了这个新的映射器 bean,以便能够将它注入到我的服务中,处理从 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 中所建议的那样。调试发现我原来的bean叫“_halObjectMapper”,所以我的factoryBean会搜索这个bean并返回:
public class ObjectMapperFactory implements FactoryBean<ObjectMapper>
ListableBeanFactory beanFactory;
public ObjectMapper getObject()
return beanFactory.getBean("_halObjectMapper", ObjectMapper.class);
...
然后在我的 Configuration 类中,我将其声明为 @Primary bean,以确保它是自动装配的首选:
@Primary
@Bean
public ObjectMapperFactory objectMapperFactory(ListableBeanFactory beanFactory)
return new ObjectMapperFactory(beanFactory);
不过,我对这个解决方案并不是 100% 满意,因为它依赖于不受我控制的 bean 的名称,而且它看起来也像是一个 hack。有更清洁的解决方案吗?
谢谢!
【问题讨论】:
我还尝试使用YAMLMapper
(扩展ObjectMapper
)作为我的自定义bean 类型,但是spring 仍然会抱怨它找到了两个ObjectMapper
类型的bean。我没想到会这样,尽管在某种程度上它是合理的。所以我今天又学到了一些新东西。这意味着如果我自动装配List<Object>
类型的字段,我将获得上下文中所有可用的bean...
查看我的答案的更新..
【参考方案1】:
您可以定义两个 ObjectMapper
bean 并将一个声明为主,例如:
@Bean("Your_id")
public ObjectMapper yamlObjectMapper()
return new ObjectMapper(new YAMLFactory());
@Bean
@Primary
public ObjectMapper objectMapper()
return new ObjectMapper();
完成后,您可以使用带有 @Qualifier
注释的 objectmapper bean,例如:
@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而不改变现有的bean(sysout
在init
方法的末尾打印两个bean)。然后,您可以使用“yamlMapper”作为限定符在任何地方自动装配它。
更新 2(来自问题作者):
“更新”中建议的解决方案有效,这是一个简化版本:
@Autowired
private DefaultListableBeanFactory beanFactory;
@PostConstruct
private void init()
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(YAMLMapper.class);
beanFactory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition());
【讨论】:
这是我尝试的第一件事,但是new ObjectMapper()
的配置方式与spring-boot 定义的方式不同,所以我得到了很多失败的测试。这就是为什么我想继续使用完全相同的 bean,而不是影响当前的 API。
这与我之前创建的答案完全相同。你为什么要这么做?
@luboskrnac 你和我的答案相差不到 2 分钟。您认为我当时阅读了您的答案、改写并输入了我的答案并附有解释吗?
其实5分钟
7:50:01
和 7:51:56
,5 分钟?【参考方案2】:
我刚刚意识到我不需要使用 FactoryBean,我也可以将常规 bean 声明为 @Primary 并使其返回原始 bean,如下所示:
@Bean
@Primary
public ObjectMapper objectMapper(@Qualifier("_halObjectMapper") ObjectMapper objectMapper)
return objectMapper;
这使得配置更加简洁,但仍然需要原始 ObjectMapper 的确切名称。不过,我想我会继续使用这个解决方案。
【讨论】:
【参考方案3】:其他选项是将自定义映射器包装到自定义对象中:
@Component
public class YamlObjectMapper
private final ObjectMapper objectMapper;
public YamlObjectMapper()
objectMapper = new ObjectMapper(new YAMLFactory());
public ObjectMapper getMapper()
return objectMapper;
不幸的是,这种方法需要在注入 YamlObjectMapper
后调用 getMapper
。
【讨论】:
【参考方案4】:我相信为 MVC 层定义显式主要对象映射器应该这样工作:
@Primary
@Bean
public ObjectMapper objectMapper()
return Jackson2ObjectMapperBuilder.json().build();
通过类型自动装配对象映射器的所有 bean 都将使用上面的 bean。您的 Yaml 逻辑可以通过 YAML_OBJECT_MAPPER_BEAN_ID
自动装配。
【讨论】:
我尝试了你的建议,但它与new ObjectMapper()
相同 - 它不会为原始映射器重用 spring-boot 配置,所以我再次得到失败的测试。
顺便说一句,Darshan Mehta 创建的答案以糟糕的方式抄袭了我的,因为这创建了使用 Spring 默认值启动的更好的 bean。
你在测试中遇到什么样的异常?
测试失败,因为当我使用 new ObjectMapper()
而不是原始的 spring-boot 实例时,输出 json 与预期值不匹配。我想有一些额外的配置与它如何格式化某些值有关。以上是关于如何在不影响原始 bean 的“客户”的情况下声明另一个 Jackson ObjectMapper?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不影响 phpMyAdmin 的情况下从 mysql 中巧妙地删除 pma 表
如何在不更改原始内容的情况下操作 NSDictionary 内容的副本