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
Related
I'm new in Spring applications, and see the big difference between configurations in springBoot and spring. So my questin is: apart from spring-boot, is there a way to setup a proper spring application(with web mvc, security, aop, ...), without any xml config file (ie : config relying only on annotations).
Yes, there is a way to do this in Spring. Spring Boot is after all an enhanced, autoconfigured Spring (with other cool features). That means that everything there is in Spring Boot should be achievable in Spring as well, but you would have do a bit/a lot of Your own extra work.
Moving straight to the point, in order to achieve what you want, you would need to undertake the following steps:
Create a class, which will store all the configuration (basically the properties you would store in the xml file) - let's call it AppConfig.class
Annotate the AppConfig.class with #Configuration - this will inform Spring that this class is the source of configuration;
Annotate the AppConfig.class with #ComponentScan("com.app") - here, You need to provide a package, from which Spring has to start component scanning in order to find Beans to be registered in Spring Container. Important note is, that it will scan the package and it's subpackages, so you would mostly want to provide here the top level package;
If you need some data to be injected into your beans, you would want to use the #PropertySource("classpath:application.properties") - I have provided here the default value, which Spring Boot uses internally in case you want to inject some data into your beans at runtime. For this to work, you need to inject into AppConfig.class an Environment.class
To show it on the example:
#Configuration
#ComponentScan("com.app")
#PropertySource("classpath:application.properties")
public class AppConfig {
// it will help to pull the properties incorporated in the file you have provided in the #PropertySource annotation
private Environment environment;
//inject it
public AppConfig(Environment environment) {
this.environment = environment;
}
// build your beans - the getProperty method accepts the key from application.properties
// file and return a value as a String. You can provide additional arguments to convert
//the value and a default value if the property is not found
#Bean
public Product product() {
return new Product(
environment.getProperty("product.name", "XXX"),
environment.getProperty("product.price", BigDecimal.class, BigDecimal.ZERO),
environment.getProperty("product.quantity", Integer.class, 10)
);
}
}
I hope that it helps
It looks like Spring always uses InMemoryRelyingPartyRegistrationRepository to return a RelyingPartyRegistrationRepository typed bean, refer to https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyRegistrationConfiguration.java.
Question: how can I inject (autowire) my own implementation of RelyingPartyRegistrationRepository? Say I would like to allow the auto wired relying party repository auto reload from database once I have SAML configuration for a certain customer updated. Is this doable?
You can provide your own bean and spring boot auto configuration will back off.
#Configuration
#EnableConfigurationProperties(Saml2RelyingPartyProperties.class)
public class SamlConfig{
#Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(Saml2RelyingPartyProperties properties) {
-- Provide custom repository implementation
}
}
You may need other changes after you create your own bean based on what you need.
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
I use spring-boot-starter-data-solr and would like to make use of the schmea cration support of Spring Data Solr, as stated in the documentation:
Automatic schema population will inspect your domain types whenever the applications context is refreshed and populate new fields to your index based on the properties configuration. This requires solr to run in Schemaless Mode.
However, I am not able to achieve this. As far as I can see, the Spring Boot starter does not enable the schemaCreationSupport flag on the #EnableSolrRepositories annotation. So what I tried is the following:
#SpringBootApplication
#EnableSolrRepositories(schemaCreationSupport = true)
public class MyApplication {
#Bean
public SolrOperations solrTemplate(SolrClient solr) {
return new SolrTemplate(solr);
}
}
But looking in Wireshark I cannot see any calls to the Solr Schema API when saving new entities through the repository.
Is this intended to work, or what am I missing? I am using Solr 6.2.0 with Spring Boot 1.4.1.
I've run into the same problem. After some debugging, I've found the root cause why the schema creation (or update) is not happening at all:
By using the #EnableSolrRepositories annotation, an Spring extension will add a factory-bean to the context that creates the SolrTemplate that is used in the repositories. This template initialises a SolrPersistentEntitySchemaCreator, which should do the creation/update.
public void afterPropertiesSet() {
if (this.mappingContext == null) {
this.mappingContext = new SimpleSolrMappingContext(
new SolrPersistentEntitySchemaCreator(this.solrClientFactory)
.enable(this.schemaCreationFeatures));
}
// ...
}
Problem is that the flag schemaCreationFeatures (which enables the creator) is set after the factory calls the afterPropertiesSet(), so it's impossible for the creator to do it's work.
I'll create an issue in the spring-data-solr issue tracker. Don't see any workaround right now, other either having a custom fork/build of spring-data or extend a bunch of spring-classes and trying to get the flag set before by using (but doubt of this can be done).
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.