I'm trying to use MetricsModule to parse the response body of /metrics endpoint. For some reason the module never gets registered with the respective ObjectMapper.
I've found that this #PostConstruct method gets erased:
#PostConstruct
private void More ...registerModulesWithObjectMappers() {
Collection<Module> modules = getBeans(this.beanFactory, Module.class);
for (ObjectMapper objectMapper : getBeans(this.beanFactory, ObjectMapper.class)) {
objectMapper.registerModules(modules);
}
I've implemented my own code to do the same thing, but I'm not sure if this is the correct way to fix this issue.
Why wouldn't JacksonAutoConfiguration support this type of auto-configuration?
That method was removed in commit "Don't register Jackson Module beans with all ObjectMappers" for issue "Make Jackson auto-configuration more consistent and predictable". In short, it was done to not mess up with every ObjectMapper bean in the container.
To ensure that your Module bean gets registered with auto-configured ObjectMapper, make sure Module creation does not depend on an instance of ObjectMapper in any way. For example, do not autowire auto-configured ObjectMapper anywhere before Module bean is created.
Related
I'm developing some libs for internal use in my company, which using spring and spring Boot.
I'm encountered a problem with my bean definition. I want to create some beans of objects that don't belong to my library, for example:
#Configuration
public class LibClass {
#Bean
public Gson gson() { return new Gson();}}
However, whenever I do this it affect the service that using my library and creating beans for them too, which means they will be forced to use my Gson or enabling overriding of beans, by defining spring.main.allow-bean-definition-overriding=true which feels wrong.
For example, if I have the same code as above in my service:
#Configuration
public class ServiceClass {
#Bean
public Gson gson() { return new Gson();}}
Spring throws
BeanDefinitionOverrideException
Is creating those kind of beans inside a library is a good practice?
How can I tell spring to use this bean just inside my libraries classes and not outside
Thanks
Edit:
As I mentioned in the comments, I don't want to force using of #Qualifier, as if they don't using it already, which we don't if there is no need to, they will get this exception and won't know why as it is not trivial to understand that some libraries using random beans. Nonetheless, if someone forget to create a bean, they can use the lib bean, by mistake, without even knowing. This could cause some nasty and unexpected behavior.
Also, the annotation #ConditionalOnMissingBean won't help me here, as I won't be able to configure specific configuration later if needed and my internal library will be affected by an unknown configuration from the user, which isn't seem like a good practice
changing the bean name and adding a qualifier should work:
#Bean
#Qualifier("gsonForInternal")
public Gson gsonForInternal() { return new Gson();}
where autowiring you should :
#Autowired
#Qualifier("gsonForInternal")
private Gson gsonForInternal;
Edit:
in case you want you can make this bean conditional:
#ConditionalOnMissingBean
#Bean
public Gson gson() { return new Gson();}
however if the library user will define its own bean:
#Bean
public Gson gson() { return new Gson();}
this means your library will use his defined Gson
so i think you should be using qualifier - if i understand your requirement correctly
I want to configure Jackson so that it automatically deserializes using constructors, without needing annotations. With Spring Boot, this works out of the box for most constructors but not single argument constructors.
Jackson 2.12 has released a configuration option to enable deserialization for single argument constructors as well:
ObjectMapper mapper = JsonMapper.builder()
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build()
However, this doesn't use the usual Feature enabling/disabling interface. How can I set this with Spring Boot?
I don't want to lose any Spring Boot autoconfiguration that is being applied to the ObjectMapper bean.
I can't define a Jackson2ObjectMapperBuilder bean because, as of Spring Boot 2.4.1, this has not yet been updated to allow constructorDetector to be set.
Defining a bean of either Jackson2ObjectMapperBuilder or ObjectMapper will prevent application of any auto-configuration for these beans, as documented.
Instead, you can define a bean of type Jackson2ObjectMapperBuilderCustomizer which is a lambda that lets you call additional methods on the Spring Boot auto-configured Jackson2ObjectMapperBuilder.
Additionally, Jackson2ObjectMapperBuilder has the method postConfigurer which is another call back which lets you call methods on the auto-configured ObjectMapper.
Putting these together:
#Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder.postConfigurer(mapper ->
mapper.setConstructorDetector(USE_PROPERTIES_BASED)
);
}
We are currently trying to implement a JSON Logging library making use of spring auto configuration or create its Jackson ObjectMapper. Our aim is to not override the spring auto configuration in class JacksonAutoConfiguration so that every customization by clients of the logging library won't be disabled.
The actual spring behavior is bean based and our main problem is that the JacksonProperties are not customizable and reusable for us. If we actually add a second bean of JacksonProperties the application start up would fail because JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.class won't be able to handle a second bean. (The Spring Boot internal one is not annotated as #Primary.)
So what we did was start reimplementing every bean like the builder, customizer and so on. But this is not very maintainable as it duplicates framework code.
Our question now is if there would be any way to adapt the way of creating data sources for jackson object mapper beans. An example of creating data sources would be a following one.
#Bean(name = "testDataSource")
#ConfigurationProperties(prefix = "test.datasource")
public HikariDataSource naeDataSource(DataSourceProperties testDataSourceProperties) {
return testDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
We know the problem would still be that overriding the object mapper would deactivate it but if you pay attention to the application context it would be much easier to offer multiple object mapper instances.
Or is there any easier or other way we did not find so far?
Versions used:
Spring-Boot 2.0.5.RELEASE
UPDATE
I forked the spring boot project, made some changes to Spring Boot Autoconfiguration module and also set up a small demo project. I do not think it is the perfect way but with this changes it would be possible to initialize own object mapper easily from configuration properties. For example you can now easily create five object mapper for five different rest templates and clients called via REST-API.
#ConfigurationProperties(prefix = "logging.jackson")
#Bean("loggingJacksonProperties")
public JacksonProperties loggingJacksonProperties() {
return new JacksonProperties();
}
#Bean
public ObjectMapper secondObjectMapper(#Qualifier("loggingJacksonProperties") JacksonProperties loggingJacksonProperties) {
return loggingJacksonProperties.initializeJackson2ObjectMapperBuilder().build();
}
Comparing-Fork: https://github.com/spring-projects/spring-boot/compare/2.1.x...mixaaaa:feature/jackson_properties_initializer
Demo-Project: https://github.com/mixaaaa/jackson-demo
Yesterday I started with upgrade from Spring Brussels-SR3 to Spring Brussels-SR6.
The Spring Boot goes from 1.5.4. to 1.5.9, Jackson goes from 2.8.8 to 2.8.10). I am using HATEOAS and HAL links. It means my Jackson configuration looks like this:
#Configuration
public class JacksonConfiguration {
private static final String SPRING_HATEOAS_OBJECT_MAPPER = "_halObjectMapper";
#Autowired
#Qualifier(SPRING_HATEOAS_OBJECT_MAPPER)
private ObjectMapper springHateoasObjectMapper;
#Primary
#Bean(name = "objectMapper")
ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
springHateoasObjectMapper.registerModules(javaTimeModule);
springHateoasObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
springHateoasObjectMapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
springHateoasObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
springHateoasObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
springHateoasObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
springHateoasObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return springHateoasObjectMapper;
}
}
It means I am reusing the _halObjectMapper bean and adding some more configurations. It had been working until I started with the upgrade.
What is wrong?
The thing that goes wrong is that after the upgrade all my serialization customizations and HAL conventions are not applied- datetime formats, indenting HAL "_links" JSON field changes to "links" ... So the _halObjectMapper is not used for serialization any more.
Any clue what could be the issue or where should I dig to figure out what is wrong?
Additional info after some debugging:
I have figured out that TypeConstrainedMappingJackson2HttpMessageConverter that uses _halObjectMapper is not used for conversion to json anymore. The reason is that it does not get to the collection of converters when starting spring. It looks like it is not created for RequestMappingHandlerAdapter bean because of some condition that skips creation when Jackson2HalModule is allready registered in some other converter (in this case ProjectingJackson2HttpMessageConverter).
Any idea what could be a cause or where to look at to figure out why the spring boot start proceeds differently?
Additional info after some more debugging:
The difference I see before and after the upgrade is that before the upgrade the ProjectingJackson2HttpMessageConverter was populated with new instance of ObjectMapper. But after the upgrade, the ObjectMapper is resolved from container so the _halObjectMapper is chosen. As a result the ProjectingJackson2HttpMessageConverter matches as a converter with registered halModule and TypeConstrainedMappingJackson2HttpMessageConverter creation is ommited for RequestMappingHandlerAdapter.
One more interesting thing is that there are two more microservices I upgraded. The difference is that one has the same issue and one is working. The one that is working has different spring security and oauth2 setup. Bean of class OAuth2RestTemplate is not defined in the microservice that is working. Microservices with OAuth2RestTemplate have the issue. The reason why I am pointing this out is that there is different in the initialization behavior in these two cases. The OAuth2RestTemplate rest template is populated with these converters too and it might affect the initialization process.
Temporary solution
As a temporary hotfix I have downgraded spring-data-commons from 1.13.6.RELEASE to 1.13.6.RELEASE. However the newer code makes more sense to me.
I am still trying to achieve some better understanding and figure out correct approach
I don't know if it is helpfull to you, but I had a very similar problem with a Spring Boot upgrade from Version 2.0.3 to 2.0.4. I still don't know what exactly caused the problem, but the solution was to create Beans for every Module I use instead of replacing the default ObjectMapper. In your case it would look something like this:
#Configuration
public class JacksonConfiguration {
#Bean
JavaTimeModule javaTimeModule () {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(DateTimeFormatter.ISO_INSTANT));
return javaTimeModule;
}
}
All Features can be set via the applications.properties file like this:
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=false
spring.jackson.deserialization.READ_DATE_TIMESTAMPS_AS_NANOSECONDS=false
and so on. For more information on how to configure the default object mapper without actually replacing it see https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-jackson-objectmapper and https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
In my (non-trivial) Spring Boot 1.5.4 application with Spring Data REST and HATEOAS leveraging Spring websockets, I have some custom resource processors, some custom controllers, and some custom repositories. Sadly, when I use constructor injection in one particular Spring #Service class for a MessageSendingOperations dependency, my custom resource processors no longer get invoked. Reverting the constructor injection restores the execution of my custom resource processors, i.e. reverting from:
private final MessageSendingOperations<String> messageTemplate;
#Autowired
public ChannelHandler(MessageSendingOperations<String> messageTemplate) {
this.messageTemplate = messageTemplate;
}
to:
#Autowired
private MessageSendingOperations<String> messageTemplate;
"re-enables" my custom resource processors which results in a null messageTemplate. So, there's a problem somewhere...but where??? Any ideas how to track this down?
Have you tried making messageTemplate a lazily injected proxy? For example:
public ChannelHandler(#Lazy MessageSendingOperations<String> messageTemplate) {
this.messageTemplate = requireNonNull(messageTemplate, "messageTemplate");
}
From the Javadoc:
In addition to its role for component initialization, this annotation
may also be placed on injection points marked with Autowired or
Inject: In that context, it leads to the creation of a lazy-resolution
proxy for all affected dependencies, as an alternative to using
ObjectFactory or Provider.
This usually affects the initialization order of your beans, in this case allowing ChannelHandler to be initialized before MessageSendingOperations. Without #Lazy, MessageSendingOperations will be initialized first.
Also: as of Spring 4.3, #Autowired is no longer required for single argument constructors.
+1 for using constructor injection and final fields.