I have defined an ObjectMapper factory class like this:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
#Factory
public class MyObjectMapper {
#Singleton
#Named("jsonObjectMapper")
public ObjectMapper getJsonObjectMapper() {
return new ObjectMapper(new JsonFactory());
}
#Singleton
#Named("yamlObjectMapper")
public ObjectMapper getYamlObjectMapper() {
return new ObjectMapper(new YAMLFactory());
}
}
Then, on client class, I tried to inject them like this:
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
#Singleton
public class MyServiceImpl implements MyService {
private ObjectMapper jsonMapper;
private ObjectMapper yamlMapper;
#Inject
#Named("jsonObjectMapper")
public void setJsonMapper(ObjectMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
#Inject
#Named("yamlObjectMapper")
public void setYamlMapper(ObjectMapper yamlMapper) {
this.yamlMapper = yamlMapper;
}
...
My goal is to have jsonMapper to be injected by the bean with #Named("jsonObjectMapper") on MyObjectMapper class, and yamlMapper with #Named("yamlObjectMapper"). But, when I tried to debug, jsonMapper and yamlMapper had the same reference, which means they are injected by the same ObjectMapper. My question is how to inject 2 different beans for json and yaml mapper on Micronaut?
Thank you!
The injection qualified by name can be done with the #Named annotation used on the method argument, and not the method itself. It means that in your case you would have to move the #Named annotation to the setJsonMapper and setYamlMapper methods arguments.
#Singleton
public class MyServiceImpl {
private ObjectMapper jsonMapper;
private ObjectMapper yamlMapper;
#Inject
public void setJsonMapper(#Named("jsonObjectMapper") ObjectMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
#Inject
public void setYamlMapper(#Named("yamlObjectMapper") ObjectMapper yamlMapper) {
this.yamlMapper = yamlMapper;
}
// ...
}
Alternatively, you could use construction injection combined with the #Named annotation for each parameter. It allows you to mark both fields as private, just to make sure these objects are not re-assigned at the runtime.
#Singleton
public class MyServiceImpl {
private final ObjectMapper jsonMapper;
private final ObjectMapper yamlMapper;
public MyServiceImpl(
#Named("jsonObjectMapper") ObjectMapper jsonMapper,
#Named("yamlObjectMapper") ObjectMapper yamlMapper) {
this.jsonMapper = jsonMapper;
this.yamlMapper = yamlMapper;
}
// ...
}
Related
I want to configure Hibernate to use Jackson's Objectmapper created by Spring to map between json and entities. In the project I'm working on I already configured Jooq to use the Spring's ObjectMapper but I'm having trouble how to configure Hibernate to use it. The goal in the end is that both Jooq and Hibernate would use the same ObjectMapper.
I checked this article by Vlad. Unfortunately all the tips given in the article don't work for the project I'm working on.
Here's an example configuration I tried
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer {
//Autowire Objectmapper created by Spring
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
ObjectMapperSupplier objectMapperSupplier = () -> objectMapper;
// Below config doesn't work since Hibernate types creates it's own mapper
hibernateProperties.put("hibernate.types.jackson.object.mapper", objectMapperSupplier);
}
Also tried the same approach by adding the Objectmapper to hibernate-types.properties.
#Used by Hibernate but cannot get reference of Spring managed ObjectMapper since this is class is called outside of Spring's context.
hibernate.types.jackson.object.mapper=path.to.ObjectMapperSupplier
Another approach I used but it fails with a NullpointerException when converting from JSON to an entity in JsonTypeDescriptor class.
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer{
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
// Underlying implementation needs some JavaType or propertyClass, otherwise when converting
// from JSON we get a nullpointer.
var jsonBinaryType = new JsonBinaryType(objectMapper);
hibernateProperties.put("hibernate.type_contributors", (TypeContributorList) () ->
Collections.singletonList((typeContributions, serviceRegistry) ->
typeContributions.contributeType(jsonBinaryType)));
}
Below is the type declaration for entity super class.
// This makes Hibernate types create it's own mapper.
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#MappedSuperclass
public abstract class Entity{
}
So, are there any possible solutions how I can hook up Spring managed ObjectMapper to Hibernate?
I finally figured this out, but it is kind of an creative solution...
TLDR: I have a bean that stores the Spring-configured objectMapper in a static field. A BeanFactoryPostProcessor ensures that this bean is initialized before Hibernate (types) tries to load / get the ObjectMapper.
hibernate.properties
hibernate.types.jackson.object.mapper=com.github.lion7.example.HibernateObjectMapperSupplier
HibernateObjectMapperSupplier.kt
package com.github.lion7.example
import com.fasterxml.jackson.databind.ObjectMapper
import com.vladmihalcea.hibernate.type.util.ObjectMapperSupplier
import org.springframework.beans.factory.config.BeanFactoryPostProcessor
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.stereotype.Component
class HibernateObjectMapperSupplier : ObjectMapperSupplier {
override fun get(): ObjectMapper =
ObjectMapperHolder.objectMapper
}
#Component
class ObjectMapperHolder(objectMapper: ObjectMapper) {
companion object {
lateinit var objectMapper: ObjectMapper
}
init {
Companion.objectMapper = objectMapper
}
}
#Component
class ObjectMapperDependencyFixer : BeanFactoryPostProcessor {
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
val beanDefinition = beanFactory.getBeanDefinition("entityManagerFactory")
val oldDependsOn = beanDefinition.dependsOn ?: emptyArray()
val newDependsOn = oldDependsOn + "objectMapperHolder"
beanDefinition.setDependsOn(*newDependsOn)
}
}
Same code as gist: https://gist.github.com/lion7/c8006b69a309e38183deb69124b888b5
A Java implementation.
#Component
public class HibernateObjectMapper implements Supplier<ObjectMapper> {
private static ObjectMapper objectMapper;
#Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
HibernateObjectMapper.objectMapper = objectMapper;
}
#Override
public ObjectMapper get() {
return objectMapper;
}
}
If you define your own JPA beans, simply add #DependsOn("hibernateObjectMapper") to their config. Otherwise you need a BeanPostProcessor to add the dependency to the autoconfigured bean:
#Component
class HibernateBeanDependencyProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
BeanDefinition beanDefinition = factory.getBeanDefinition("entityManagerFactory");
String[] dependsOn = beanDefinition.getDependsOn();
dependsOn = dependsOn == null ? new String[]{} : dependsOn;
String[] newDependsOn = new String[dependsOn.length + 1];
System.arraycopy(dependsOn, 0, newDependsOn, 1, dependsOn.length);
newDependsOn[0] = "hibernateObjectMapper";
beanDefinition.setDependsOn(newDependsOn);
}
}
As for the property, hibernate.types.* don't work when set programmatically. The library looks directly in the hibernate.properties, hibernate-types.properties, and application.properties files.
I think that I've found solution to do it programmatically (without magic with fixing dependency graph).
HibernateConfiguration.kt
#Configuration(proxyBeanMethods = false)
class HibernateConfiguration {
#Bean
fun hibernatePropertiesCustomizer(
objectMapper: ObjectMapper // Thanks to that Spring can create correct dependency graph
): HibernatePropertiesCustomizer =
HibernatePropertiesCustomizer { hibernateProperties ->
HibernateObjectMapperSupplier.objectMapper = objectMapper
hibernateProperties["hibernate.types.jackson.object.mapper"] = HibernateObjectMapperSupplier::class.qualifiedName
}
}
HibernateObjectMapperSupplier.kt
class HibernateObjectMapperSupplier : Supplier<ObjectMapper> {
override fun get(): ObjectMapper {
return objectMapper
}
companion object {
lateinit var objectMapper: ObjectMapper
}
}
System.getProperties().put(
Configuration.PropertyKey.JACKSON_OBJECT_MAPPER.getKey(),
MyObjectMapperSupplier.class.getName()
);
In some mapper classes, I need to use an autowired ObjectMapper to transform String to JsonNode or verse-vera. I can acheive my goal by using the field injection with #autowired. But it's not suitable for unit test so I'd like to try using the constructor injection.
My current working code with the field injection:
#Mapper(componentModel = "spring")
public class CustomMapper {
#autowired
ObjectMapper mapper;
}
I try to convert it to the constructor injection so I can provide constructor argument in my unit test:
#Mapper(componentModel = "spring")
public class CustomMapper {
ObjectMapper mapper;
public CustomMapper(ObjectMapper mapper) {
this.mapper = mapper;
}
}
But I get a Constructor in CustomMapper cannot be applied to the given type error during compilation.
How do I fix it? Or is there other better way to map String to JsonNode in Mapstruct?
Constructor injection cannot be used in the mapper definition. Only in the mapper implementation.
However, for unit testing I'd suggest that you use setter injection.
Your mapper will then look like:
#Mapper( componentModel = "spring")
public class CustomMapper {
protected ObjectMapper mapper;
#Autowired
public void setMapper(ObjectMapper mapper) {
this.mapper = mapper;
}
}
1)The MapStruct has good feature:
#Mapper(componentModel = "spring", uses ={ObjectMapper.class}, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
2)You could do it this way:
#Mapper(componentModel = "spring")
#RequiredArgsConstructor //lombok annotation, which authowire your field via constructor
public class CustomMapper {
private final ObjectMapper mapper;
}
But still you could do it via field.You stiil should mock it in tests in both cases. Just remember to use #InjectMocks
public CustomMapperTest {
#InjectMocks
private CustomMapper customMapper;
#Mock
private ObjectMapper objectMapper
#BeforeEach
void setUp() {
customMapper= new CustomMapperImpl();
MockitoAnnotations.initMocks(this);
when(objectMapper.map()).thenReturn(object);
}
#Test
void shouldMap() {
Object toObject = customerMapper.map(fromObject);
assertThat(toObject)
.hasFieldWithValue("fieldName", fromObject.getField());
}
}
I am not sure how to register my custom objectMapper that I created below as a bean and inject it as dependency into other objects via constructor, or Autowire
#SpringBootApplication
public class DemoApplication {
#Bean
//how to register it as a bean here and inject wherever I need to via #Inject or #Autowire
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper = new ObjectMapper();
public ObjectMapperProvider() {
this.objectMapper.disable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return objectMapper;
}
}
Be careful with that. You are mixing Jax-RS and Spring, but you have to know something: Spring does not implement fully the Jax-RS specification... The reason ? Spring MVC was developed about the same time as JAX-RS, and after JAX-RS was released, they never migrate to implement this (who would have anyway) ?
The best way to declare your own ObjectMapper with Spring would be the following:
#SpringBootApplication
public class DemoApplication {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// DO what you want;
return objectMapper;
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
}
Then, you can use #Autowired to inject your ObjectMapper in the class that needs it. (check this link if you want: Configuring ObjectMapper in Spring)
Hope it helps.
I'm having trouble with mocking an ObjectMapper bean with mokito.
My class containing the ObjectMapper :
public class ServiceParent{
#Autowired
protected ObjectMapper objectMapper;
public void someMethod(){
...
Map<String, String> mapResponse = objectMapper.readValue("some json", new TypeReference<Map<String, String>>(){});
}
Other class extending previous one
public class ServiceChild extends ServiceParent{
...
}
My test class :
#SpringBootTest
public class TestService{
#Mock
ObjectMapper objectMapper;
#InjectMocks
ServiceChild serviceChild;
#Test
public void test(){
Map<String, String> mapResponse=new HashMap<String, String>();
mapResponse.put("access_token", "token_bouhon");
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class))).thenReturn(mapResponse);
}
So when I debug this, in ServiceParent, objectMapper isn't null but readValue(...) return null.
Do you have an idea on how to return the correct mocked object?
Thanks
I faced the same NPE with the objectmapper. This issue is resolved after adding ,
my test class has class level annotation with
#RunWith(MockitoJUnitRunner.class)
and the fix
#Before
public void setupBefore() {
MockitoAnnotations.initMocks(this);
}
#Mock creates a new mock. It's equivalent to calling mock(SomeClass.class)
#MockBean creates a mock, like #Mock, but also replaces any bean already in the application context with the same type with that mock. So any code which Autowires that bean will get the mock.
You need to use #MockBean
I want response by using single function like:
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response getVolume(){
...enter code here
return Response.ok().entity(VolDetail).build();
}
Output shoulb be like:
xml:
<volume>
<status>available</status>
</volume>
JSON:
{"volume":{"status":"available"}}
where volume is a POJO class.
The problem is that I am not getting root element in JSON. I tried JSON object binding but its not working properly.
Assuming you're using Jackson. You can configure the ObjectMapper to WRAP_ROOT_VALUE. You would do that in the ContextResolver. With Jackson 1.x, it would look like
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperContextResolver() {
mapper.configure(Feature.WRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
With Jackson 2.x, it would look like
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperContextResolver() {
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Your POJO should be annotated with #XmlRootElement(name = "volume") or #JsonRootName("volume")
If you don't want all your objects wrapped, you can configure different mappers for different classes, as seen here
Edit
With the above solution, only #JsonRootName will work. The reason is that by using our own ObjectMapper, we override the behavior of a JAXB annotation support configuration. We could explicitly add the support back by mapper.registerModule(new JaxbAnnotationModule());