Reputation: 2880
I have a Spring boot app written in Kotlin where I would like to enable caching in Redis. I'd like to have the objects stored as serialized JSON and ideally don't want to have to register each type that could be potentially cached. I have some configuration that mostly works, with a big caveat.
@Bean
fun redisCacheConfiguration(): RedisCacheConfiguration {
val objectMapper =
ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
val serializer = GenericJackson2JsonRedisSerializer(objectMapper)
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
}
I'm having a little trouble understanding the different values for DefaultTyping
but NON_FINAL
seems to be the most expansive. However, since objects in Kotlin are final by default, this only works for objects flagged as "open". Ideally I'd like to avoid having to "open" objects just so they can be cached.
Is there some other way I can make this work?
Upvotes: 6
Views: 7924
Reputation: 1256
I had a problem since my data classes were extending some interfaces, so generic would not do the trick, I end up with this solution, its a custom serialiser and deserialiser, the generic would just save time compiled getter as a variable and break the deserialise
@Configuration
@EnableCaching
class CachingConfiguration() : CachingConfigurerSupport() {
@Bean
fun configureRedisAction(): ConfigureRedisAction? {
return ConfigureRedisAction.NO_OP
}
@Autowired
private lateinit var redisConnectionFactory: RedisConnectionFactory
companion object {
const val CACHE_KEY = "cache-key"
}
@Bean
override fun cacheManager(): CacheManager? {
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.withCacheConfiguration(CACHE_KEY, cacheConfig<User>(ofMinutes(5)))
.build()
}
private inline fun <reified T> cacheConfig(ttl: Duration): RedisCacheConfiguration {
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(fromSerializer(object : RedisSerializer<Any> {
val mapper = ObjectMapper().registerModule(ParameterNamesModule())
override fun serialize(t: Any?): ByteArray? {
return mapper.writeValueAsBytes(t)
}
override fun deserialize(bytes: ByteArray?): Any? {
return try {
mapper.readValue(bytes!!, T::class.java) as Any
} catch (e: Exception) {
null
}
}
})
)
.entryTtl(ttl)
}
}
Upvotes: 0
Reputation: 1
You can look this:
https://github.com/endink/caching-kotlin
Its support both jackson and kryo
Upvotes: -1
Reputation: 751
The issues have been solved. Therefore we can remove @Cacheble
hack from the code. You have to modify your ObjectMapper
with the next implementation
val om = ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.activateDefaultTyping(BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Any::class.java)
.build(), ObjectMapper.DefaultTyping.EVERYTHING)
val serializer = GenericJackson2JsonRedisSerializer(om)
Fixed Maven Jackon dependency
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr2</version>
</dependency>
Upvotes: 2
Reputation: 190
I had the same problem. You should use "open" classes. But this will not help you with data classes, because you cannot make them "open".
There is a plugin called "all-open" where you can define annotations. If you use these annotations classes become "open", even data classes.
spring-kotlin plugin uses "all-open" plugin under the hood, so spring annotations like @Service, @Component etc. make classes open for AOP, because proxying requires you to inherit from classes.
If you use spring-kotlin plugin, there is nice annotation that makes sense for you problem, it is used in Spring Cache, its name is @Cacheable. If you use @Cacheable on your classes, they will become open and save their type-info to json (ex: {@class: "com.example.MyClass", ...}) when you include this code:
val objectMapper =
ObjectMapper()
.registerModule(KotlinModule())
.registerModule(JavaTimeModule())
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
val serializer = GenericJackson2JsonRedisSerializer(objectMapper)
More details: https://kotlinlang.org/docs/reference/compiler-plugins.html
Shortly: You don't have to do anything except adding @Cacheable annotation to the classes you want, and it fits by sense also IMO.
Upvotes: 10