Spring Data Redis
  1. Spring Data Redis
  2. DATAREDIS-175

Make JacksonJsonRedisSerializer NOT depend on a specific domain object class

    Details

      Description

      I am trying to use Redis with Spring (and) looking at the configuration required to acheive this, I've noticed that "JacksonJsonRedisSerializer" requires a domain object class for instantiation.

      My application has multiple domain objects and i would like to enable redis caching on several rest API methods (say — getObject1, getObject2 etc.). Each of these methods return a different object.

      The default JDK serialization works fine (has to do with enough information stored for deserializing). However, JSON serialization using "Object" class doesn't work (i guess as expected).

      Tried using "CompositeCacheManager" (one cacheManager per domain object that needs to be cached.....not elegant but gave it a try due to limitation with spring config) but am stuck due to https://jira.springsource.org/browse/SPR-8696

      Please advise as to how to proceed. Appreciate all the help.

        Activity

        Hide
        Amarkanth added a comment -

        Can someone please recommend on how to proceed ?

        Show
        Amarkanth added a comment - Can someone please recommend on how to proceed ?
        Hide
        Jennifer Hickey added a comment -

        All RedisSerializers need to declare a specific type, so not sure we could really make the change you are asking for.

        Until SPR-8696 is implemented, the main problem seems to be that RedisCacheManager.getCache() will always instantiate a new RedisCache instead of returning null, so CompositeCacheManager is not guaranteed to return a RedisCache with the correct RedisTemplate injected.

        For example, consider the XML config for 2 domain objects, Person and Address. Each has its own RedisTemplate with its own instance of JacksonJsonRedisSerializer:

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
        	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:c="http://www.springframework.org/schema/c"
        	xmlns:context="http://www.springframework.org/schema/context"
        	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        	http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
        	http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context-3.0.xsd">
        
        	<cache:annotation-driven />
        
                <bean id="stringSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        
        	<bean id="personRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        		<property name="connectionFactory" ref="redisConnectionFactory" />
        		<property name="valueSerializer">
        			<bean
        				class="org.springframework.data.redis.serializer.JacksonJsonRedisSerializer">
        				<constructor-arg value="org.jen.samples.Person" />
        			</bean>
        		</property>
        		<property name="keySerializer" ref="stringSerializer"/>
        	</bean>
        	
        	<bean id="addressRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        		<property name="connectionFactory" ref="redisConnectionFactory" />
        		<property name="valueSerializer">
        			<bean
        				class="org.springframework.data.redis.serializer.JacksonJsonRedisSerializer">
        				<constructor-arg value="org.jen.samples.Address" />
        			</bean>
        		</property>
        		<property name="keySerializer" ref="stringSerializer"/>
        	</bean>
        
        	<bean id="redisConnectionFactory"
        		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" />
        
        	<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
        		<property name="cacheManagers">
        			<list>
        				<ref bean="personCacheManager" />
        				<ref bean="addressCacheManager" />
        			</list>
        		</property>
        	</bean>
        
        	<bean id="personCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
        		c:template-ref="personRedisTemplate" />
        		
        	<bean id="addressCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
        		c:template-ref="addressRedisTemplate" />
        
        </beans>
        

        Unfortunately, with this configuration, the CompositeCacheManager will always return a RedisCache with the personRedisTemplate for the "address" cache. This results in deserialization errors when reading from the cache.

        You could maybe work around this by implementing your own CompositeCacheManager or RedisCacheManager that can pick the correct redisTemplate based on the cache name (using reflection or bean name parsing, i.e. the "address" cache name should use the "addressRedisTemplate" bean in its RedisCache)

        Show
        Jennifer Hickey added a comment - All RedisSerializers need to declare a specific type, so not sure we could really make the change you are asking for. Until SPR-8696 is implemented, the main problem seems to be that RedisCacheManager.getCache() will always instantiate a new RedisCache instead of returning null, so CompositeCacheManager is not guaranteed to return a RedisCache with the correct RedisTemplate injected. For example, consider the XML config for 2 domain objects, Person and Address. Each has its own RedisTemplate with its own instance of JacksonJsonRedisSerializer: <?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http: //www.springframework.org/schema/beans" xmlns:cache= "http: //www.springframework.org/schema/cache" xmlns:c= "http://www.springframework.org/schema/c" xmlns:context= "http: //www.springframework.org/schema/context" xmlns:xsi= "http: //www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http: //www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http: //www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http: //www.springframework.org/schema/context http: //www.springframework.org/schema/context/spring-context-3.0.xsd"> <cache:annotation-driven /> <bean id= "stringSerializer" class= "org.springframework.data.redis.serializer.StringRedisSerializer" /> <bean id= "personRedisTemplate" class= "org.springframework.data.redis.core.RedisTemplate" > <property name= "connectionFactory" ref= "redisConnectionFactory" /> <property name= "valueSerializer" > <bean class= "org.springframework.data.redis.serializer.JacksonJsonRedisSerializer" > <constructor-arg value= "org.jen.samples.Person" /> </bean> </property> <property name= "keySerializer" ref= "stringSerializer" /> </bean> <bean id= "addressRedisTemplate" class= "org.springframework.data.redis.core.RedisTemplate" > <property name= "connectionFactory" ref= "redisConnectionFactory" /> <property name= "valueSerializer" > <bean class= "org.springframework.data.redis.serializer.JacksonJsonRedisSerializer" > <constructor-arg value= "org.jen.samples.Address" /> </bean> </property> <property name= "keySerializer" ref= "stringSerializer" /> </bean> <bean id= "redisConnectionFactory" class= "org.springframework.data.redis.connection.jedis.JedisConnectionFactory" /> <bean id= "cacheManager" class= "org.springframework.cache.support.CompositeCacheManager" > <property name= "cacheManagers" > <list> <ref bean= "personCacheManager" /> <ref bean= "addressCacheManager" /> </list> </property> </bean> <bean id= "personCacheManager" class= "org.springframework.data.redis.cache.RedisCacheManager" c:template-ref= "personRedisTemplate" /> <bean id= "addressCacheManager" class= "org.springframework.data.redis.cache.RedisCacheManager" c:template-ref= "addressRedisTemplate" /> </beans> Unfortunately, with this configuration, the CompositeCacheManager will always return a RedisCache with the personRedisTemplate for the "address" cache. This results in deserialization errors when reading from the cache. You could maybe work around this by implementing your own CompositeCacheManager or RedisCacheManager that can pick the correct redisTemplate based on the cache name (using reflection or bean name parsing, i.e. the "address" cache name should use the "addressRedisTemplate" bean in its RedisCache)
        Hide
        Jennifer Hickey added a comment -

        Don't see any actionable items for SDR here

        Show
        Jennifer Hickey added a comment - Don't see any actionable items for SDR here
        Hide
        David Coleman added a comment -

        Can you simply not create your own implementation of RedisSerializer that will map to/from Json based upon object type? The code below seems to work.

        JsonRedisSerializer.java
        public class JsonRedisSerializer implements RedisSerializer<Object> {
        
        	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
        	
        	static {
        		OBJECT_MAPPER.enableDefaultTyping();
        		OBJECT_MAPPER.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        	}
        	
        	@Override
        	public Object deserialize(byte[] bytes) throws SerializationException {
        		if (ArrayUtils.isEmpty(bytes)) {
        			return null;
        		}
        		try {
        			return OBJECT_MAPPER.readValue(bytes, Holder.class).getValue();
        		} catch (Exception e) {
        			throw new SerializationException("Error on converting bytearray to json object", e);
        		}
        	}
        	
        	@Override
        	public byte[] serialize(Object t) throws SerializationException {
        		if (t == null) {
        			return new byte[0];
        		}
        		try {
        			return OBJECT_MAPPER.writeValueAsBytes(new Holder(t));
        		} catch (Exception e) {
        			throw new SerializationException("Error on writing json object to bytearray", e);
        		}
        	}
        	
        }
        
        Holder.java
        @JsonTypeInfo(use = Id.NONE)
        public class Holder {
        
        	/**
        	 * Wrapped value.
        	 */
        	@JsonProperty("v")
        	private Object value;
        
        	public Holder() {
        
        	}
        
        	public Holder(final Object value) {
        		this.setValue(value);
        	}
        
        	public Object getValue() {
        		return value;
        	}
        
        	public void setValue(Object value) {
        		this.value = value;
        	}
        
        }
        
        JsonRedisSerializerTest.java
        
        @Test
        	public void serializeAndDeserializeSuccess() {
        		ResponseDto dto = new ResponseDto();
        		dto.setProductype("Shoes");
        		dto.setName("Super Awesome");
        		
        		byte[] serializedJsonObject = serializer.serialize(dto);
        		ResponseDto deserializeJsonObject = (ResponseDto) serializer.deserialize(serializedJsonObject);
        		
        		assertEquals(deserializeJsonObject, dto);
        		
        	}
        
        Show
        David Coleman added a comment - Can you simply not create your own implementation of RedisSerializer that will map to/from Json based upon object type? The code below seems to work. JsonRedisSerializer.java public class JsonRedisSerializer implements RedisSerializer< Object > { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { OBJECT_MAPPER.enableDefaultTyping(); OBJECT_MAPPER.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false ); } @Override public Object deserialize( byte [] bytes) throws SerializationException { if (ArrayUtils.isEmpty(bytes)) { return null ; } try { return OBJECT_MAPPER.readValue(bytes, Holder.class).getValue(); } catch (Exception e) { throw new SerializationException( "Error on converting bytearray to json object" , e); } } @Override public byte [] serialize( Object t) throws SerializationException { if (t == null ) { return new byte [0]; } try { return OBJECT_MAPPER.writeValueAsBytes( new Holder(t)); } catch (Exception e) { throw new SerializationException( "Error on writing json object to bytearray" , e); } } } Holder.java @JsonTypeInfo(use = Id.NONE) public class Holder { /** * Wrapped value. */ @JsonProperty( "v" ) private Object value; public Holder() { } public Holder( final Object value) { this .setValue(value); } public Object getValue() { return value; } public void setValue( Object value) { this .value = value; } } JsonRedisSerializerTest.java @Test public void serializeAndDeserializeSuccess() { ResponseDto dto = new ResponseDto(); dto.setProductype( "Shoes" ); dto.setName( "Super Awesome" ); byte [] serializedJsonObject = serializer.serialize(dto); ResponseDto deserializeJsonObject = (ResponseDto) serializer.deserialize(serializedJsonObject); assertEquals(deserializeJsonObject, dto); }

          People

          • Assignee:
            Unassigned
            Reporter:
            Amarkanth
          • Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: