在我的Spring Boot项目中,我使用默认的Jackson对象映射器.我想将新的对象映射器添加到Spring上下文中,并开始在新的地方使用它,但也保留默认的对象映射器.添加新的@Bean定义将覆盖默认的对象映射器.如何添加新的对象映射器Bean而不覆盖以前的对象映射器Bean?

推荐答案

是的,@ConditionalOnMissingBean是(很难不可能的)黑客.用一个简单的技巧(亚洲哲学),我们可以绕过这个问题/让它根本不成问题:

将您的(1+,自动配置,@ConditionalOnMissing...)Bean包装在其他/定制/a"包装器"中.(代价是:参考1+/考虑差异/更复杂)

提到的MappingJackson2HttpMessageConverter(auto-config here)具有这种(内置)功能(&;用途),以"http转换"的方式映射到多个对象映射器.

因此,对于(通用的,例如基于java.util.Map的)东西:

class MyWrapper<K, V> {
  final Map<K, V> map;
  public MyWrapper(Map<K, V> map) {
    this.map = map;
  }
  public Map<K, V> getMap() {
    return map;
  }
}

我们可以go 电汇过go :

@Bean
MyWrapper<String, ObjectMapper> myStr2OMWrapper(/*ObjectMapper jacksonOm*/) {
  return new MyWrapper() {
    {
      // map.put(DEFAULT, jacksonOm);
      getMap().put("foo", fooMapper());
      getMap().put("bar", barMapper());
    }
  };
}

..其中fooMapper()barMapper()可以指(静态/实例)no-bean方法:

private static ObjectMapper fooMapper() {
  return new ObjectMapper()
      .configure(SerializationFeature.INDENT_OUTPUT, true) // just a demo...
      .configure(SerializationFeature.WRAP_ROOT_VALUE, true); // configure/set  as see fit...
}
private static ObjectMapper barMapper() {
  return new ObjectMapper()
      .configure(SerializationFeature.INDENT_OUTPUT, false) // just a demo...
      .configure(SerializationFeature.WRAP_ROOT_VALUE, false); // configure/set more...
}

(已经)测试/使用时间:

package com.example.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoAppTests {

  @Autowired
  MyWrapper<String, ObjectMapper> my;
  @Autowired
  ObjectMapper jacksonOM;

  @Test
  void contextLoads() {
    System.err.println(jacksonOM);
    Assertions.assertNotNull(jacksonOM);
    my.getMap().entrySet().forEach(e -> {
      System.err.println(e);
      Assertions.assertNotNull(e.getValue());
    });
  }
}

fingerprint (例如)

...
com.fasterxml.jackson.databind.ObjectMapper@481b2f10
bar=com.fasterxml.jackson.databind.ObjectMapper@577bf0aa
foo=com.fasterxml.jackson.databind.ObjectMapper@7455dacb
...
Results:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
...

抱歉,这个测试不能验证(单独的)配置,只能验证:(视觉上不同的)非空对象映射器.


如何启用(多个!)my.custom.jackson.*自动配置,是一个更复杂的问题…(它不像my.custom.datasource.*配置那样简单;

有:

@Bean
@Primary // ! for auto config, we need one primary (whether it is "spring.jackson"  ... adjust;)
@ConfigurationProperties("spring.jackson")
JacksonProperties springJacksonProps() {
  return new JacksonProperties();
}

@Bean
@ConfigurationProperties("foo.jackson")
JacksonProperties fooProps() {
  return new JacksonProperties();
}

@Bean
@ConfigurationProperties("bar.jackson")
JacksonProperties barProps() {
  return new JacksonProperties();
}

我们已经可以加载和区分(完全)配置,如下所示:

spring.jackson.locale=en_US
spring.jackson.time-zone=UTC
# ... all of spring.jackson @see org.springframework.boot.autoconfigure.jackson.JacksonProperties
foo.jackson.locale=en_US
foo.jackson.time-zone=PST
# ... just for demo purpose
bar.jackson.locale=de_DE
bar.jackson.time-zone=GMT+1

也(没问题)将它们(props )传递给相应的(静态[foo|bar]Mapper)方法...但是然后呢?(如果你对它很在行,你可以在这里停止阅读!:)

不幸的是,according ("state of art") code(用"om Builder"连接JacksonProperties)不是公共的(即不可扩展/可插拔).

相反,自动配置提供(如果未定义/@ConditionalOnMissingBean):

因此,最简单的方法似乎(最新的)是:

  • 钢/采用the code(非-/实施Jackson2ObjectMapperBuilderCustomizer)
  • 按照建筑商/制图员的要求建造(从"偷来的"+财产).

例如(判断+生产前测试!)非接口,返回Jackson2ObjectMapperBuilder,模拟自动配置,不应用(其他)定制器/设置:

// ...
import com.fasterxml.jackson.databind.Module; // !! not java.lang.Module ;)
// ...
private static class MyStolenCustomizer {

  private final JacksonProperties jacksonProperties;
  private final Collection<Module> modules;
  // additionally need/want this:
  private final ApplicationContext applicationContext;
  // copy/adopt from spring-boot:
  private static final Map<?, Boolean> FEATURE_DEFAULTS = Map.of(
      SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false,
      SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false
  );

  public MyStolenCustomizer(
    ApplicationContext applicationContext,
    JacksonProperties jacksonProperties,
    Collection<Module> modules
  ) {
      this.applicationContext = applicationContext;
      this.jacksonProperties = jacksonProperties;
      this.modules = modules;
  }
  // changed method signature!!
  public Jackson2ObjectMapperBuilder buildCustom() {
    // mimic original (spring-boot) bean:
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.applicationContext(applicationContext);
    // without (additional!) customizers:
    if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
      builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion());
    }
    if (this.jacksonProperties.getTimeZone() != null) {
      builder.timeZone(this.jacksonProperties.getTimeZone());
    }
    configureFeatures(builder, FEATURE_DEFAULTS);
    configureVisibility(builder, this.jacksonProperties.getVisibility());
    configureFeatures(builder, this.jacksonProperties.getDeserialization());
    configureFeatures(builder, this.jacksonProperties.getSerialization());
    configureFeatures(builder, this.jacksonProperties.getMapper());
    configureFeatures(builder, this.jacksonProperties.getParser());
    configureFeatures(builder, this.jacksonProperties.getGenerator());
    configureDateFormat(builder);
    configurePropertyNamingStrategy(builder);
    configureModules(builder);
    configureLocale(builder);
    configureDefaultLeniency(builder);
    configureConstructorDetector(builder);
    // custom api:
    return builder; // ..alternatively: builder.build();
  }
  // ... rest as in https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java#L223-L341

要电汇modules,我们可以(希望,像最初一样)依赖:

@Autowired
ObjectProvider<com.fasterxml.jackson.databind.Module> modules

要对它们进行初始化,请执行以下操作:

@Bean
MyStolenCustomizer fooCustomizer(ApplicationContext context, @Qualifier("fooProps") JacksonProperties fooProperties, ObjectProvider<Module> modules) {
  return new MyStolenCustomizer(context, fooProperties, modules.stream().toList());
}

@Bean
MyStolenCustomizer barCustomizer(ApplicationContext context, @Qualifier("barProps") JacksonProperties barProperties, ObjectProvider<Module> modules) {
  return new MyStolenCustomizer(context, barProperties, modules.stream().toList());
}

..并像这样使用它们:

@Bean
MyWrapper<String, Jackson2ObjectMapperBuilder> myStr2OMBuilderWrapper(
    @Qualifier("fooCustomizer") MyStolenCustomizer fooCustomizer,
    @Qualifier("barCustomizer") MyStolenCustomizer barCustomizer) {
  return new MyWrapper(
      Map.of(
          "foo", fooCustomizer.buildCustom(),
          "bar", barCustomizer.buildCustom()
      )
  );
}

...避免"双重定制"/使JacksonAutoConfiguration处于启用/完整/激活状态.

问题:时间/更新(/外部代码)!

Java相关问答推荐

Annotation @ Memphier无法正常工作,并表示:class需要一个bean,但找到了2个bean:

Java取消任务运行Oracle查询通过JDBC—连接中断,因为SQLSTATE(08006),错误代码(17002)IO错误:套接字读取中断

有没有一种方法使保持活动设置专用于java.net.http.HttpClient的一个实例

S的字符串表示是双重精确的吗?

所有 case 一起输入时输出错误,而单独放置时输出正确

如何从错误通道回复网关,使其不会挂起

解释左移在Java中的工作原理

如何在Spring Java中从数据库列中获取JSON中的具体数据

如何在不作为类出现的表上执行原生查询?

为什么mvn编译生命周期阶段不只是编译已更改的java文件?

为什么相同的数据条码在视觉上看起来不同?

在Java Spring JPA中插入包含对其他实体的引用的列

在WHILE()循环初始化部分中声明和初始化变量的Java语法?

Java中的一个错误';s stdlib SocksSocketImpl?

使用原子整数的共享计数器并发增量

Springboot应用程序无法识别任何@RestController或@Service,我认为@Repository也无法识别

Cucumber中第二个网页的类对象未初始化

message.acknowledge()没有';在使用Spring Boot在ActiveMQ中读取消息后,t将消息出列

Spring Integration SFTP 连接失败 - 无法协商 kex 算法的密钥交换

类型安全:从 JSONArray 到 ArrayList> 的未经判断的转换