Skip to content

Commit

Permalink
redis cache 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
punkryn committed May 19, 2024
1 parent 0f18aea commit ecd526d
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tiketeer.TiketeerWaiting.annotation


@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class RedisCacheable(
val key: String = ""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.tiketeer.TiketeerWaiting.aspect

import com.fasterxml.jackson.databind.ObjectMapper
import com.tiketeer.TiketeerWaiting.annotation.RedisCacheable
import io.github.oshai.kotlinlogging.KotlinLogging
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.data.redis.core.ReactiveRedisTemplate
import org.springframework.stereotype.Component
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.stream.Collectors
import com.tiketeer.TiketeerWaiting.util.AspectUtils

private val logger = KotlinLogging.logger {}

@Aspect
@Component
class RedisCacheAspect(
private val redisTemplate: ReactiveRedisTemplate<String, String>,
private val objectMapper: ObjectMapper,
private val aspectUtils: AspectUtils) {
@Around("execution(public * *(..)) && @annotation(com.tiketeer.TiketeerWaiting.annotation.RedisCacheable)")
fun redisReactiveCacheable(joinPoint: ProceedingJoinPoint): Any {
val methodSignature = joinPoint.signature as MethodSignature
val method = methodSignature.method

val returnType = method.returnType
val annotation = method.getAnnotation(RedisCacheable::class.java)

val key = aspectUtils.resolveKey(joinPoint, annotation.key)

val typeReference = aspectUtils.getTypeReference(method)

logger.info { "Evaluated Redis cacheKey: $key" }

val cachedValue = redisTemplate.opsForValue().get(key)

if (returnType.isAssignableFrom(Mono::class.java)) {
return cachedValue
.map { v ->
objectMapper.readValue(v, typeReference)
}
.switchIfEmpty(Mono.defer {
(joinPoint.proceed(joinPoint.args) as Mono<*>)
.map { t ->
redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(t)).subscribe()
t
}
})
} else if (returnType.isAssignableFrom(Flux::class.java)) {
return cachedValue
.flatMapMany { v ->
Flux.fromIterable(
(v as List<String>).stream()
.map { e -> objectMapper.readValue(e, typeReference) }
.collect(Collectors.toList()) as List<*>
)

}
.switchIfEmpty(Flux.defer {
(joinPoint.proceed(joinPoint.args) as Flux<*>)
.collectList()
.map { t ->
redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(t)).subscribe()
t
}
.flatMapMany { Flux.fromIterable(it) }
})
}

throw RuntimeException("RedisReactiveCacheGet: Annotated method has unsupported return type, expected Mono<?> or Flux<?>")
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.tiketeer.TiketeerWaiting.configuration

import com.tiketeer.TiketeerWaiting.util.AspectUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.RedisStandaloneConfiguration
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.ReactiveRedisTemplate
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.expression.spel.standard.SpelExpressionParser

@Configuration
@Profile("!test")
Expand All @@ -37,7 +37,7 @@ class RedisConfig {
}

@Bean
fun cacheManager(connectionFactory: RedisConnectionFactory?): RedisCacheManager {
return RedisCacheManager.create(connectionFactory!!)
fun aspectUtils(expressionParser: SpelExpressionParser) : AspectUtils {
return AspectUtils(expressionParser)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tiketeer.TiketeerWaiting.configuration

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.expression.spel.standard.SpelExpressionParser

@Configuration
class SpelConfig {
@Bean
fun spelExpressionParser(): SpelExpressionParser {
return SpelExpressionParser()
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.tiketeer.TiketeerWaiting.domain.ticketing.repository

import com.tiketeer.TiketeerWaiting.annotation.RedisCacheable
import com.tiketeer.TiketeerWaiting.domain.ticketing.Ticketings
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import reactor.core.publisher.Mono
import java.util.UUID

interface TicketingRepository : ReactiveCrudRepository<Ticketings, UUID> {
@RedisCacheable(key = "#id")
override fun findById(id: UUID) : Mono<Ticketings>
}
49 changes: 49 additions & 0 deletions src/main/kotlin/com/tiketeer/TiketeerWaiting/util/AspectUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.tiketeer.TiketeerWaiting.util

import com.fasterxml.jackson.core.type.TypeReference
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.reflect.CodeSignature
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

@Component
class AspectUtils(private val expressionParser: SpelExpressionParser) {
fun resolveKey(joinPoint: JoinPoint, key: String): String {
if (StringUtils.hasText(key)) {
if (key.contains("#") || key.contains("'")) {
val parameterNames: Array<String> = getParamNames(joinPoint)
val args = joinPoint.args
val context = StandardEvaluationContext()
for (i in parameterNames.indices) {
context.setVariable(parameterNames[i], args[i])
}
val value = expressionParser.parseExpression(key).getValue(context)
return value.toString()
}
return key
}
throw RuntimeException("RedisReactiveCache annotation missing key")
}

private fun getParamNames(joinPoint: JoinPoint): Array<String> {
val codeSignature = joinPoint.signature as CodeSignature
return codeSignature.parameterNames
}

fun getTypeReference(method: Method): TypeReference<Any> {
return object : TypeReference<Any>() {
override fun getType(): Type {
return getMethodActualReturnType(method)
}
}
}

private fun getMethodActualReturnType(method: Method): Type {
return (method.genericReturnType as ParameterizedType).actualTypeArguments[0]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package com.tiketeer.TiketeerWaiting.domain.waiting.controller
import com.tiketeer.TiketeerWaiting.auth.constant.JwtMetadata
import com.tiketeer.TiketeerWaiting.configuration.EmbeddedRedisConfig
import com.tiketeer.TiketeerWaiting.configuration.R2dbcConfiguration
import com.tiketeer.TiketeerWaiting.domain.ticketing.Ticketings
import com.tiketeer.TiketeerWaiting.domain.ticketing.repository.TicketingRepository
import com.tiketeer.TiketeerWaiting.domain.waiting.usecase.GetRankAndTokenUseCase
import com.tiketeer.TiketeerWaiting.domain.waiting.usecase.dto.GetRankAndTokenCommandDto
import com.tiketeer.TiketeerWaiting.testHelper.TestHelper
Expand All @@ -20,7 +18,6 @@ import org.springframework.context.annotation.Import
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.test.web.reactive.server.WebTestClient
import java.nio.ByteBuffer
import java.time.LocalDateTime
import java.util.Date
import java.util.UUID
Expand Down

0 comments on commit ecd526d

Please sign in to comment.