Spring Boot: как создать свой бин без затрагивания поведения автоконфигурации?

Вопрос к знатокам Spring Boot.
Мне нужно заинжектить кастомный ObjectMapper в свой компонент, но при этом я не хочу ломать дефолтный мэппер, созданный автоматически в JacksonAutoConfiguration, потому как во всех остальных местах в приложении используется именно дефолтный вариант, дополненный настройками из application.yml / bootstrap.yml: всякие кастомные настройки сериализации/десериализации дат, enum-ов и т.п. То есть я не хочу трогать дефолтный вариант, потому что он уже правильно настроен на работу со стандартными опциями настроек.

Я создаю свой конфиг :
@Configuration
public class CustomMapperConfig {

    @Bean(name = "myCustomMapper")
    public ObjectMapper myCustomMapper() {
    	ObjectMapper mapper = new ObjectMapper();
        //do some customizing
        return mapper;
    }
}


И хочу использовать различные реализации мэппера:
@Component
public class MyComponent {
	
	@Autowired @Qualifier("myCustomMapper")
	ObjectMapper myCustomMapper; // хочу использовать тут свой бин 

	@Autowired
	ObjectMapper objectMapper;
    // хочу тут использовать стандартный бин из автоконфига,  
    // но инжектится все равно моя кастомная реализация
}


Но в результате все места, где используется ObjectMapper, начинают использовать именно мой кастомный бин.
Разницы нет - инжектить по имени, явно указывать Qualifier или не указывать ничего вовсе - все равно будет использована моя собственная реализация.

Дело в том, что в JacksonAutoConfiguration используется такая настройка:
@Bean
@Primary
@ConditionalOnMissingBean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
	return builder.createXmlMapper(false).build();
}


Несмотря на то, что тут указано @Primary, в приоритете срабатывает @ConditionalOnMissingBean: фреймворк видит, что у меня есть своя реализация и не выполняет создание из автоконфига.

Чтобы обойти это, я пробовал следующие варианты:
1) Заинжектить JacksonAutoConfiguration -> в CustomMapperConfig, чтобы задать явно порядок инициализации + установить низший приоритет своей реализации
@Configuration
@AllArgsConstructor
public class CustomMapperConfig {
    private final JacksonAutoConfiguration autoConfiguration;

    @Bean(name = "myCustomMapper")
    @Order(Ordered.LOWEST_PRECEDENCE)
    public ObjectMapper myCustomMapper() {
        //...
    }
}

Это не работает: да, инициализация классов проходит в нужном порядке, но мой метод создания все равно отрабатывает раньше, чем метод в JacksonAutoConfiguration. Соответственно, следом опять отрабатывает поведение @ConditionalOnMissingBean

2) Заинжектить дефолтный мэппер себе в конфиг и донастроить (такой вариант меня бы тоже устроил, - кастомная настройка не помешала бы дефолтному поведению в остальных местах):
вариант 1:
@Configuration
@AllArgsConstructor
public class CustomMapperConfig {

    private final ObjectMapper defaultMapper;

    @Bean(name = "myCustomMapper")
    public ObjectMapper myCustomMapper() {
        //customize
        return defaultMapper;
    }
}


вариант 2:
@Configuration
public class CustomMapperConfig {

    @Bean(name = "myCustomMapper")
    public ObjectMapper myCustomMapper(ObjectMapper defaultMapper) {
        //customize
        return defaultMapper;
    }
}


В результате падает вот с такой ошибкой:
The dependencies of some of the beans in the application context form a cycle: customMapperConfig defined in file...


Можно конечно именно в своей конфигурации создавать бин ровно таким же образом, как это выполняется в автоконфигурации, фактически продублировав её, чтобы он потреблял все те же самые настройки, что и дефолтный. Но так делать очень бы не хотелось: это копипаста и хак библиотеки.

Можно ли как-то сохранить создание дефолтного бина из автоконфига? Или то, что там указано @ConditionalOnMissingBean жестко превалирует над остальными настройками?
  • Вопрос задан
  • 2202 просмотра
Пригласить эксперта
Ответы на вопрос 3
@JeRRy_froyo
могу предложить такой вариант "донастраивания":
@Configuration
public class CustomMapperConfig {

    public CustomMapperConfig(ApplicationContext context) {
        ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
        //customize
    }
}


UPD:
здесь же можно зарегистрировать свой бин (если все же нужны отдельные объекты) через context.getAutowireCapableBeanFactory()

если использовать только дефолтный, то наверно можно сделать еще проще:
public CustomMapperConfig(ObjectMapper objectMapper) {
    //customize
}
Ответ написан
Комментировать
BorLaze
@BorLaze
Java developer
На днях тоже копался с подобным вопросом.
Так вот, автоконфигурация всегда отрабатывает после тех конфигов, что определены в приложении.

Что до вопроса, я бы попробовал
@Bean(name = "myCustomMapper")
    @DependsOn({"objectMapper"})
    public ObjectMapper myCustomMapper(@Lazy @Qualifier("objectMapper") ObjectMapper defaultMapper) {
        //customize
        return defaultMapper;
    }

В общем, поиграться с подобными директивами - возможно, получится втолковать спрингу, что конкретно ты хочешь получить на входе.
Ответ написан
Комментировать
@lexas
Как мне кажется лучше пойти по пути меньшего сопротивления и создать два ОМ. Один на замену основному, а второй кастом с autowire-candidate=false.

Да, лишния декларация бина, но читабельнее и без воркараундоа.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы