Skip to content

Commit

Permalink
patch: add multiple conditionals to prevent Bean clashes #91
Browse files Browse the repository at this point in the history
Spring.session.store-type was removed to follow the Spring Boot 3 way. The RedisConfiguration is dependent on Redis being on the classpath, so this property is redundant anyway.
  • Loading branch information
rspiegl committed Jan 14, 2024
1 parent 2f22db3 commit 9e4082d
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 23 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ class QueryDslArtifactRepositoryImpl(

TBD

> **_NOTE:_** The property spring.session.store-type was removed in Spring Boot 3. It was already a redundant setting as
> the store type is determined by the presence of the spring-boot-starter-data-redis.
### Scheduling

The module `platform-spring-scheduling` integrates [Shedlock](https://github.com/lukas-krecan/ShedLock) with Spring-Boot by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ package io.cloudflight.platform.spring.caching.autoconfigure
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.ObjectMapper
import io.cloudflight.platform.spring.caching.serializer.SafeRedisSessionSerializer
import io.cloudflight.platform.spring.json.ObjectMapperFactory
import mu.KotlinLogging
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.cache.CacheProperties
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
import org.springframework.cache.annotation.CachingConfigurer
import org.springframework.cache.annotation.EnableCaching
Expand All @@ -26,12 +25,11 @@ import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.fromSerializer
import org.springframework.data.redis.serializer.RedisSerializer
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession
import java.time.Duration

@AutoConfiguration(before = [RedisAutoConfiguration::class])
@EnableCaching(order = 1000)
@Import(CachingAutoConfiguration.RedisConfiguration::class, CachingAutoConfiguration.RedisSessionConfig::class)
@Import(CachingAutoConfiguration.RedisConfiguration::class)
class CachingAutoConfiguration {
/**
* Same condition as in RedisAutoConfiguration
Expand Down Expand Up @@ -71,14 +69,14 @@ class CachingAutoConfiguration {
}

@Bean
fun platformRedisCacheManagerBuilderCustomizer(cacheConfiguration: RedisCacheConfiguration):
RedisCacheManagerBuilderCustomizer {
fun platformRedisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer {
return RedisCacheManagerBuilderCustomizer { builder ->
builder.transactionAware()
}
}

@Bean
@ConditionalOnMissingBean(RedisTemplate::class)
@Lazy
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> {
return RedisTemplate<Any, Any>().apply {
Expand All @@ -96,16 +94,6 @@ class CachingAutoConfiguration {
}
}

@Configuration
@ConditionalOnProperty(value = ["spring.session.store-type"], havingValue = "redis")
@EnableRedisHttpSession
class RedisSessionConfig {
@Bean
fun springSessionDefaultRedisSerializer(): RedisSerializer<*> {
return SafeRedisSessionSerializer(RedisSerializer.java())
}
}

companion object {
private val JSON = createJsonSerializer()
private val LOG = KotlinLogging.logger { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.cloudflight.platform.spring.caching.autoconfigure

import io.cloudflight.platform.spring.caching.serializer.SafeRedisSessionSerializer
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisOperations
import org.springframework.data.redis.serializer.RedisSerializer
import org.springframework.session.SessionRepository
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession

@AutoConfiguration(after = [CachingAutoConfiguration::class], before = [RedisAutoConfiguration::class])
@Import(SessionAutoConfiguration.RedisSessionConfig::class)
class SessionAutoConfiguration {

@Configuration
@ConditionalOnClass(RedisOperations::class)
@ConditionalOnBean(RedisConnectionFactory::class)
@ConditionalOnMissingBean(SessionRepository::class)
@EnableRedisHttpSession
class RedisSessionConfig

@Bean
@ConditionalOnClass(RedisOperations::class)
@ConditionalOnBean(SessionRepository::class)
fun springSessionDefaultRedisSerializer(): RedisSerializer<*> {
return SafeRedisSessionSerializer(RedisSerializer.java())
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
io.cloudflight.platform.spring.caching.autoconfigure.CachingAutoConfiguration
io.cloudflight.platform.spring.caching.autoconfigure.CachingAutoConfiguration
io.cloudflight.platform.spring.caching.autoconfigure.SessionAutoConfiguration
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
package io.cloudflight.platform.spring.caching

import io.cloudflight.platform.spring.caching.autoconfigure.CachingAutoConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.autoconfigure.cache.CacheProperties
import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.data.redis.cache.FixedDurationTtlFunction
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import java.time.Duration


class CachingAutoConfigurationTest {

private val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CachingAutoConfiguration::class.java))

@Test
fun testRedisTypeInfoSerialization() {
val serializer = CachingAutoConfiguration.createJsonSerializer()
Expand All @@ -12,8 +33,74 @@ class CachingAutoConfigurationTest {
val bytes = serializer.serialize(data)
val obj = serializer.deserialize(bytes, java.lang.Object::class.java) as DataDto

assert(data.foo == obj.foo)
assertThat(obj.foo).isEqualTo(data.foo)
}
}

data class DataDto(var foo: String)
private data class DataDto(var foo: String)

@Test
fun redisTemplateBackOff() {
this.contextRunner.withUserConfiguration(CustomRedisTemplateConfiguration::class.java)
.run { context ->
assertThat(context).hasSingleBean(RedisTemplate::class.java)
assertThat(context).getBean("myCustomRedisTemplate")
.isSameAs(context.getBean(RedisTemplate::class.java))
}
}

@Test
fun redisCacheConfigurationWithCacheProperties() {
this.contextRunner.withUserConfiguration(CustomCachePropertiesConfiguration::class.java)
.run { context ->
assertThat(context).hasSingleBean(RedisCacheConfiguration::class.java)
val redisCacheConfiguration = context.getBean(RedisCacheConfiguration::class.java)
assertThat(redisCacheConfiguration).extracting(RedisCacheConfiguration::getTtlFunction)
.isInstanceOf(FixedDurationTtlFunction::class.java)
.extracting("duration")
.isEqualTo(Duration.ofSeconds(15))
assertThat(redisCacheConfiguration.allowCacheNullValues).isFalse()
assertThat(redisCacheConfiguration.usePrefix()).isTrue()
assertThat(redisCacheConfiguration.getKeyPrefixFor("")).isEqualTo("foo::")
}
}

@Configuration(proxyBeanMethods = false)
@EnableCaching
private class BasicRedisConfiguration {
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return mock(RedisConnectionFactory::class.java)
}

@Bean
fun cacheManager(connectionFactory: RedisConnectionFactory): CacheManager {
return RedisCacheManager.create(connectionFactory)
}
}

@Configuration(proxyBeanMethods = false)
@Import(BasicRedisConfiguration::class)
private class CustomRedisTemplateConfiguration {
@Bean
fun cacheProperties(): CacheProperties {
return CacheProperties()
}

@Bean
fun myCustomRedisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<*, *>? {
return mock(RedisTemplate::class.java)
}
}

@Configuration(proxyBeanMethods = false)
@Import(BasicRedisConfiguration::class)
private class CustomCachePropertiesConfiguration {
@Bean
fun cacheProperties(): CacheProperties {
return CacheProperties().apply {
redis.timeToLive = Duration.ofSeconds(15)
redis.keyPrefix = "foo"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.cloudflight.platform.spring.caching

import io.cloudflight.platform.spring.caching.autoconfigure.SessionAutoConfiguration
import io.cloudflight.platform.spring.caching.serializer.SafeRedisSessionSerializer
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.test.context.runner.ApplicationContextRunner
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.RedisSerializer
import org.springframework.session.SessionRepository
import org.springframework.session.data.redis.RedisIndexedSessionRepository
import org.springframework.session.data.redis.RedisSessionRepository

class SessionAutoConfigurationTest {

private val contextRunner = ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration::class.java))

@Test
fun defaultRedisHttpSession() {
this.contextRunner.withUserConfiguration(BasicRedisConfiguration::class.java)
.run { context ->
Assertions.assertThat(context).hasSingleBean(SessionRepository::class.java)
Assertions.assertThat(context).getBean("sessionRepository")
.isInstanceOf(RedisSessionRepository::class.java)
}
}

@Test
fun redisHttpSessionBackOff() {
this.contextRunner.withUserConfiguration(CustomRedisIndexedHttpSessionConfiguration::class.java)
.run { context ->
Assertions.assertThat(context).hasSingleBean(SessionRepository::class.java)
Assertions.assertThat(context).getBean("sessionRepository")
.isInstanceOf(RedisIndexedSessionRepository::class.java)
}
}

@Test
fun safeRedisSessionSerializerWithDefaultSessionRepository() {
this.contextRunner.withUserConfiguration(BasicRedisConfiguration::class.java)
.run { context ->
Assertions.assertThat(context).hasSingleBean(RedisSerializer::class.java)
Assertions.assertThat(context).getBean("springSessionDefaultRedisSerializer")
.isInstanceOf(SafeRedisSessionSerializer::class.java)

}
}

@Test
fun safeRedisSessionSerializerWithUserSessionRepository() {
this.contextRunner.withUserConfiguration(CustomRedisIndexedHttpSessionConfiguration::class.java)
.run { context ->
Assertions.assertThat(context).hasSingleBean(RedisSerializer::class.java)
Assertions.assertThat(context).getBean("springSessionDefaultRedisSerializer")
.isInstanceOf(SafeRedisSessionSerializer::class.java)

}
}

@Configuration(proxyBeanMethods = false)
@EnableCaching
private class BasicRedisConfiguration {
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return mock(RedisConnectionFactory::class.java)
}

@Bean
fun cacheManager(connectionFactory: RedisConnectionFactory): CacheManager {
return RedisCacheManager.create(connectionFactory)
}
}


@Configuration(proxyBeanMethods = false)
private class CustomRedisIndexedHttpSessionConfiguration {

@Bean
fun redisTemplate(): RedisTemplate<*, *>? {
return mock(RedisTemplate::class.java)
}

@Bean
fun sessionRepository(): RedisIndexedSessionRepository {
return mock(RedisIndexedSessionRepository::class.java)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
spring:
session:
store-type: redis
cache:
redis:
time-to-live: PT5M
time-to-live: PT5M

0 comments on commit 9e4082d

Please sign in to comment.