How to inject ObjectMapper in spring - java

I have a batch job written using Spring Batch
I have a config file below:
#Configuration
public class ObjectMapperConfig {
#Bean
public ObjectMapper objectMapper(){
return new ObjectMapper();
}
}
I have Json Line aggregator as below:
public class JsonLineAggregator<T> implements LineAggregator<T> {
private final ObjectMapper mapper = new ObjectMapper();
#Override
public String aggregate(final T item) {
String result = null;
try {
result = mapper.writeValueAsString(item);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return result;
}
}
I want to inject objectMapper and don't want to create it inside the JsonLineAggregator class. Any idea how can I implement it using DI ?

You can use #Autowired annotation to inject the dependency in spring like this:
#Autowired
private final ObjectMapper mapper;
And Spring will inject the ObjectMapper bean created by you in ObjectMapperConfig class

Related

Axon Framework: Aggregate Autowired Bean throws NullPointerException in Test

Springboot and Axon: Basically I am unit testing an aggregate that uses three different ObjectMapper instances, each with a different configuration. These are defined in config class :
#Configuration
public class JacksonConfiguration {
#Bean(name="flatMapper")
#Primary
public ObjectMapper flatMapper(){
return new ObjectMapper();
}
#Bean(name="unknownPropertiesMapper")
public ObjectMapper unknownPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean(name="nullPropertiesMapper")
public ObjectMapper nullPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}
They are injected and used in my aggregate as follow:
#Aggregate
#Data
#Component
public class MyAggregate {
#Autowired
#Qualifier("flatMapper")
private ObjectMapper flatMapper;
#Autowired
#Qualifier("unknownPropertiesMapper")
private ObjectMapper unknownPropertiesMapper;
#Autowired
#Qualifier("nullPropertiesMapper")
private ObjectMapper nullPropertiesMapper;
#AggregateIdentifier
private String id;
//Methods and Handlers: a method is using "unknownPropertiesMapper" is "changedKeySet"
when I run SpringBootApplication everything is properly instanciated and working as expected, but when testing I get NullPointerException over thier instances:
#ContextConfiguration(classes = JacksonConfiguration.class)
#SpringBootTest(classes = OccurrenceAggregate.class)
#ComponentScan(basePackageClasses = MyAggregate.class)
public class AggregateTest {
private FixtureConfiguration<MyAggregate> fixture;
#BeforeEach
public void setUp() {
fixture = new AggregateTestFixture<>(MyAggregate.class);
}
#Test
public void myTest() {
fixture.givenNoPriorActivity()....
}
test console:
org.axonframework.test.AxonAssertionError: The command handler threw an unexpected exception
Expected <ANYTHING> but got <exception of type [NullPointerException]>. Stack trace follows:
java.lang.NullPointerException
at com.example.business.aggregates.MyAggregate.changedKeySet(MyAggregate.java:185)
changedKeySet() is throwing NPE because its using unknownPropertiesMapper and it is value is null.
as I mentionned it works fine when I run the Main class but not in tests (Junit5).
The Aggregate is not set up correctly. The correct way to inject a Spring Bean into the Aggregate is to add it to the CommandHandler method.
#CommandHandler
public void handle(ACommand cmd, ObjectMapper flatMapper) {
In the test fixture you can inject it this way:
fixture.registerInjectableResource(flatMapper);

SpringData Redis Repository with complex key

We try to use the Spring Data CrudRepository in our project to provide persistency for our domain objects.
For a start I chose REDIS as backend since in a first experiment with a CrudRepository<ExperimentDomainObject, String> it seemd, getting it running is easy.
When trying to put it in our production code, things got more complicated, because here our domain objects were not necesseriliy using a simple type as key so the repository was CrudRepository<TestObject, ObjectId>.
Now I got the exception:
No converter found capable of converting from type [...ObjectId] to type [byte[]]
Searching for this exception, this answer which led my to uneducated experimenting with the RedisTemplate configuration. (For my experiment I am using emdedded-redis)
My idea was, to provide a RedisTemplate<Object, Object> instead of RedisTemplate<String, Object> to allow using the Jackson2JsonRedisSerializer to do the work as keySerializer also.
Still, calling testRepository.save(testObject) fails.
Please see my code:
I have public fields and left out the imports for the brevity of this example. If they are required (to make this a MVCE) I will happily provide them. Just leave me a comment.
dependencies:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation group: 'redis.clients', name: "jedis", version: '2.9.0'
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
}
RedisConfiguration:
#Configuration
#EnableRedisRepositories
public class RedisConfiguration {
#Bean
JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
#Bean
public RedisTemplate<Object, Object> redisTemplate() {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
final RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
template.setDefaultSerializer(jackson2JsonRedisSerializer);
template.setKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setEnableDefaultSerializer(true);
return template;
}
}
TestObject
#RedisHash("test")
public class TestObject
{
#Id public ObjectId testId;
public String value;
public TestObject(ObjectId id, String value)
{
this.testId = id;
this.value = value; // In experiment this is: "magic"
}
}
ObjectId
#EqualsAndHashCode
public class ObjectId {
public String creator; // In experiment, this is "me"
public String name; // In experiment, this is "fool"
}
TestRepository
#Repository
public interface TestRepository extends CrudRepository<TestObject, ObjectId>
{
}
EmbeddedRedisConfiguration
#Configuration
public class EmbeddedRedisConfiguration
{
private final redis.embedded.RedisServer redisServer;
EmbeddedRedisConfiguration(RedisProperties redisProperties)
{
this.redisServer = new redis.embedded.RedisServer(redisProperties.getPort());
}
#PostConstruct
public void init()
{
redisServer.start();
}
#PreDestroy
public void shutdown()
{
redisServer.stop();
}
}
Application:
#SpringBootApplication
public class ExperimentApplication
{
public static void main(String[] args)
{
SpringApplication.run(ExperimentApplication.class, args);
}
}
Not the desired Answer:
Of course, I might introduce some special ID which is a simple datatype, e.g. a JSON-String which I build manually using jacksons ObjectMapper and then use a CrudRepository<TestObject, String>.
What I also tried in the meantime:
RedisTemplate<String, String>
RedisTemplate<String, Object>
Autowireing a RedisTemplate and setting its default serializer
Registering a Converter<ObjectId, byte[]> to
An autowired ConverterRegistry
An autowired GenericConversionService
but apparently they have been the wrong ones.
Basically, the Redis repositories use the RedisKeyValueTemplate under the hood to store data as Key (Id) and Value pair. So your configuration of RedisTemplate will not work unless you directly use it.
So one way for you will be to use the RedistTemplate directly, something like this will work for you.
#Service
public class TestService {
#Autowired
private RedisTemplate redisTemplate;
public void saveIt(TestObject testObject){
ValueOperations<ObjectId, TestObject> values = redisTemplate.opsForValue();
values.set(testObject.testId, testObject);
}
}
So the above code will use your configuration and generate the string pair in the Redis using the Jackson as the mapper for both the key and the value.
But if you want to use the Redis Repositories via CrudRepository you need to create reading and writing converters for ObjectId from and to String and byte[] and register them as custom Redis conversions.
So let's create reading and writing converters for ObjectId <-> String
Reader
#Component
#ReadingConverter
#Slf4j
public class RedisReadingStringConverter implements Converter<String, ObjectId> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public ObjectId convert(String source) {
try {
return objectMapper.readValue(source, ObjectId.class);
} catch (IOException e) {
log.warn("Error while converting to ObjectId.", e);
throw new IllegalArgumentException("Can not convert to ObjectId");
}
}
}
Writer
#Component
#WritingConverter
#Slf4j
public class RedisWritingStringConverter implements Converter<ObjectId, String> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convert(ObjectId source) {
try {
return objectMapper.writeValueAsString(source);
} catch (JsonProcessingException e) {
log.warn("Error while converting ObjectId to String.", e);
throw new IllegalArgumentException("Can not convert ObjectId to String");
}
}
}
And the reading and writing converters for ObjectId <-> byte[]
Writer
#Component
#WritingConverter
public class RedisWritingByteConverter implements Converter<ObjectId, byte[]> {
Jackson2JsonRedisSerializer<ObjectId> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(ObjectId.class);
#Override
public byte[] convert(ObjectId source) {
return jackson2JsonRedisSerializer.serialize(source);
}
}
Reader
#Component
#ReadingConverter
public class RedisReadingByteConverter implements Converter<byte[], ObjectId> {
Jackson2JsonRedisSerializer<ObjectId> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(ObjectId.class);
#Override
public ObjectId convert(byte[] source) {
return jackson2JsonRedisSerializer.deserialize(source);
}
}
And last add the Redis custom conversations. Just put the code into the RedisConfiguration
#Bean
public RedisCustomConversions redisCustomConversions(RedisReadingByteConverter readingConverter,
RedisWritingByteConverter redisWritingConverter,
RedisWritingStringConverter redisWritingByteConverter,
RedisReadingStringConverter redisReadingByteConverter) {
return new RedisCustomConversions(Arrays.asList(readingConverter, redisWritingConverter, redisWritingByteConverter, redisReadingByteConverter));
}
So now after the converters are created and registered as custom Redis Converters the RedisKeyValueTemplate can use them and your code should work as expected.

Can I use reference to Jackson ObjectMapper when I registered JacksonFeature for JerseyTest?

Is it possible somehow to reuse Jackson ObjectMapper reference in my test classes when I testing my rest services using Jersey test framework.
I registered JacksonFeature in my abstract class from which my test classes extends.
public abstract class AbstractRestIntegrationTest extends JerseyTest {
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(getResourceClass());
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(MultiPartFeature.class);
return resourceConfig;
}
}
EDIT
In my test class I calling the rest service, which returns to me json. Then I want to parse this json by jackson and check if it contains objects. And my question is if I can reuse that ObjectMapper in test method from Jersey when I register JacksonFeature.
public class ManagedElementIntegrationTest extends AbstractRestIntegrationTest {
#Override
protected Class<?> getResourceClass() {
return ManagedElementRestService.class;
}
#Test
public void testGetManagedElementById() throws IOException {
IManagedElementService managedElementService = getBeanOfClass(IManagedElementService.class);
ManagedElement me = prepareManagedElementObject();
try {
when(managedElementService.getUpdatedApplication(anyString())).thenReturn(me);
} catch (ManagedElementNotFoundException e) {
fail("Exception not expected: " + e);
}
String response = target("me/app").request().accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
JsonNode actualObj = mapper.readTree(response);
assertNotNull(actualObj);
assertNotNull(actualObj.get("mapPosition"));
assertNull(actualObj.get("alarms"));
// code continues below.
}
}

How can I define a custom ObjectMapper bean without overriding the one used by Spring Boot

I have a Spring Boot web app with several #RestController classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper:
#Configuration
public class Config{
#Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper:
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one #Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using #Qualifier.
The problem here is that, creating default bean like:
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations.
The correct way to create default ObjectMapper that will be used by spring, is:
#Bean
#Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
Since I didn't want to touch Spring's default ObjectMapper, creating a #Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:
#Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
My qualifier is an annotation which is annotated with #Qualifier:
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper using my custom annotation, like this:
#Repository
public class MyDao {
#Autowired
public MyDao(DataSource dataSource, #PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as #Primary.
Then gives your custom ObjectMapper a name and use it with #Qualifier annotation.
#Configuration
public class Config{
//This bean will be selected for rest
#Bean
#Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
#Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
#Resource("custonmObjectMapper") will do the same of #Autowired and #Qualifier together
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).

How do I get a reference to the Jackson Object Mapper in a jersey2 / hk2 application

I have a jersey2 application configured for JSON support via Jackson, adding
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
in the POM file and
public MyApplication() {
...
register(JacksonFeature.class)
...
}
in my application. Everything works, my resources get deserialized POJOs as arguments
#POST #Consumes(MediaType.APPLICATION_JSON)
public void blah(MyPojo p) {
...
}
Now one of thoese resources needs a reference to Jackson's ObjectMapper to do some deserialization on its own. I've tried doing something like
#Inject
public MyResource(#Context ObjectMapper mapper) {
...
}
or
#GET
public String foo(#Context ObjectMapper mapper) {
...
}
but in both cases the reference to mapper is null. How can I inject a reference to the ObjectMapper in my resources?
First there is no default ObjectMapper used by the Jackson provider. It doesn't use an ObjectMapper at all actually. It makes use of other Jackson APIs to handle the (de)serialization.
If you want to use/inject a single ObjectMapper instance, then you should just create a Factory for it
public class ObjectMapperFactory implements Factory<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
#Override
public ObjectMapper provide() {
return mapper;
}
#Override
public void dispose(ObjectMapper t) {}
}
Then bind it
register(new AbstractBinder(){
#Override
public void configure() {
bindFactory(ObjectMapperFactory.class)
.to(ObjectMapper.class).in(Singleton.class);
}
});
One thing should be noted is that any configuration of the ObjectMapper is not thread safe. So say you tried to configure it from your resource method, those operations are not thread safe.
Another thing to note with the Jackson provider, is that if we provide a ContextResolver, like mentioned by #Laurentiu L, then the Jackson provider will switch to using our ObjectMapper. In which case, if you want to use that same ObjectMapper, then you can look it up in the Factory. For example
public class ObjectMapperFactory implements Factory<ObjectMapper> {
private final Providers providers;
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperFactory(#Context Providers providers) {
this.providers = providers;
}
#Override
public ObjectMapper provide() {
ContextResolver<ObjectMapper> resolver = providers.getContextResolver(
ObjectMapper.class, MediaType.APPLICATION_JSON);
if (resolver == null) { return mapper; }
return resolver.getContext(null);
}
#Override
public void dispose(ObjectMapper t) {}
}
For the above to work (use a single ObjectMapper), you need to make sure to implement the ContextResolver<ObjectMapper>, and make sure to annotation the ContextResolver with the corresponding #Produces and #Consumes media types.
Aside from the JacksonFeature you need to register a ContextResolver for ObjectMapper.
Simple example from the Documentation at 9.1.4.2. Configure and register
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(Feature.INDENT_OUTPUT, true);
return result;
}
// ...
}
Complete code example
available on Github
You will also need to register it
.register(MyObjectMapperProvider.class)

Categories

Resources