Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make JacksonJsonRedisSerializer NOT depend on a specific domain object class [DATAREDIS-175] #753

Closed
spring-projects-issues opened this issue May 6, 2013 · 7 comments
Labels
status: declined A suggestion or change that we don't feel we should currently apply type: bug A general bug

Comments

@spring-projects-issues
Copy link

Amarkanth opened DATAREDIS-175 and commented

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


0 votes, 6 watchers

@spring-projects-issues
Copy link
Author

Amarkanth commented

Can someone please recommend on how to proceed ?

@spring-projects-issues
Copy link
Author

Jennifer Hickey commented

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)

@spring-projects-issues
Copy link
Author

Jennifer Hickey commented

Don't see any actionable items for SDR here

@spring-projects-issues
Copy link
Author

David Coleman commented

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.

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);
		}
	}
	
}
@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;
	}

}

@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);
		
	}

@spring-projects-issues
Copy link
Author

Prashant Deva commented

why is this considered closed? there is no solution for this even now.

@spring-projects-issues
Copy link
Author

Alex Paransky commented

Enabling default typing allows ObjectMapper to write and read arbitrary objects from the stream (similar to the way JavaSerializer works). The type of the object is written out so that when it's de-serialized ObjectMapper knows specifically which class to instantiate.

This is the reason, i think this ticket is closed. Enable default typing and you should be good to go with a single serializer that can serialize/deserialize any type of JSON object using Jackson's ObjectMapper

@spring-projects-issues
Copy link
Author

Alex Paransky commented

This is the key that makes above code/solution work:

OBJECT_MAPPER.enableDefaultTyping();

@spring-projects-issues spring-projects-issues added type: bug A general bug status: declined A suggestion or change that we don't feel we should currently apply labels Dec 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply type: bug A general bug
Projects
None yet
Development

No branches or pull requests

1 participant