1

I'm using redisson-spring-boot-starter 3.13.2 and Kotlin for cache but I get the following exception:

java.lang.IllegalArgumentException: java.io.NotSerializableException: com.service.message.State
    at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:338)
    at org.redisson.RedissonMapCache.fastPutOperationAsync(RedissonMapCache.java:843)
    at org.redisson.RedissonMapCache.fastPutAsync(RedissonMapCache.java:746)
    at org.redisson.RedissonMapCache.fastPut(RedissonMapCache.java:720)
    at org.redisson.spring.cache.RedissonCache.put(RedissonCache.java:107)
    at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87)
    at org.springframework.cache.interceptor.CacheAspectSupport$CachePutRequest.apply(CacheAspectSupport.java:820)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:429)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
    at ...
Caused by: java.io.NotSerializableException: com.service.message.State
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:272)
    at org.jboss.marshalling.AbstractObjectOutput.writeObject(AbstractObjectOutput.java:58)
    at org.jboss.marshalling.AbstractMarshaller.writeObject(AbstractMarshaller.java:111)
    at org.redisson.codec.MarshallingCodec$4.encode(MarshallingCodec.java:176)
    at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:336)
    ... 115 common frames omitted
Caused by: org.jboss.marshalling.TraceInformation: null

This is how my config class looks like:

@Configuration
@EnableCaching
class CacheConfig {
    @Bean
    fun cacheManager(client: RedissonClient): CacheManager {
        val config = org.redisson.spring.cache.CacheConfig()
        config.ttl = 10 * 60 * 1000
        return RedissonSpringCacheManager(client, mapOf("State" to config))
    }
}

Type that I'm trying to cache:

data class State(
    val phoneCode: String,
    val phoneNumber: String
)

And this is how I'm trying to cache it:

@CachePut(value = ["State"], key = "#id")
    fun initializeState(phoneAreaCode: String, phoneNumber: String, id: String): State {
        ...
        return State(
            phoneCode = "...",
            phoneNumber = "...",
            ...
        )
    }

Edit: I managed to by pass this error by adding JsonJacksonCodec() to RedissonSpringCacheManager(client, mapOf("State" to config), JsonJacksonCodec()) but now I'm having trouble deserializing:

unable to decode data. channel: [id: 0x81736df6, L:/...], reply: ReplayingDecoderByteBuf(ridx=1704, widx=1704), command: (EVAL), params: [local value = redis.call('hget', KEYS[1], ARGV[2]); if value == false then return nil; end; local t,..., 5, ...
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1695]
zhaider
  • 595
  • 2
  • 9
  • 26
  • Have you tried marking your State class with @Serializable annotation? – eray Sep 22 '20 at 20:47
  • Not the annotation but I tried by implementing Serializable and it seems to work but as soon as I add another type in the object, it starts to complain again. The other object type is beyond my control since it's generated by swagger. I also remember from other implementations like Lettuce etc that you don't need to actually implement the serializable interface. – zhaider Sep 23 '20 at 13:13
  • all of the objects that you need to put in to the redis should implement serializable. if the object you are trying to serialize is a 3rd party class, then you should mark it as a transient. But if you still need that 3rd party class and its data, you could try defining a JSONSerializer in your redisconfig. I am not very familiar with kotlin however. – eray Sep 23 '20 at 13:15
  • Can you tell me how to define a JSONSerializer or deserializer in redis config? Java code will do. – zhaider Sep 23 '20 at 13:34
  • Okay after adding codec the deserialization is broken as described in the original post. – zhaider Sep 23 '20 at 15:34

2 Answers2

3

I have created a specific Kotlin JsonJacksonCodec:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator
import org.redisson.codec.JsonJacksonCodec

class JsonJacksonKotlinCodec(objectMapper: ObjectMapper) : JsonJacksonCodec(objectMapper) {

    override fun initTypeInclusion(mapObjectMapper: ObjectMapper) {
        mapObjectMapper.activateDefaultTyping(BasicPolymorphicTypeValidator.builder()
            .allowIfBaseType(Any::class.java)
            .build(), ObjectMapper.DefaultTyping.EVERYTHING)
    }
}

This Codec overrides all type inclusion logic and includes typing for everything. Data classes, sealed classes and everything works.

To use it:

val config = Config()
val codec = JsonJacksonKotlinCodec(ObjectMapper())
codec.objectMapper.registerKotlinModule()
config.codec = codec
config.useSingleServer().address = redisConfig.host
redissonClient = Redisson.create(config).reactive()
Elfogre
  • 105
  • 2
  • 12
0

There's a cleaner way. If spring-web jar is on the classpath, Spring Boot will create an ObjectMapper with Kotlin support enabled. If not present, simply add com.fasterxml.jackson.module:jackson-module-kotlin jar to classpath, and do the following:

Create Kotlin-aware ObjectMapper:

@Bean
fun objectMapper(): ObjectMapper = jacksonObjectMapper()

Implement RedissonAutoConfigurationCustomizer callback interface:

@Bean
fun redisConfigCustomizer(): RedissonAutoConfigurationCustomizer {
    return RedissonAutoConfigurationCustomizer {
        it.codec = JsonJacksonKotlinCodec(objectMapper())
    }
}

That's it.

Abhijit Sarkar
  • 21,927
  • 20
  • 110
  • 219