Cacheable ์ด๋
Spring์์๋ ๊ฐ๋จํ๊ฒ ์ด๋ ธํ ์ด์ ์ ๊ธฐ๋ฐ์ผ๋ก ์บ์ฑ ์ ๋ต์ ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
๊ฐ ์บ์ ๋ฒค๋์ฌ์์ ์ ๊ณตํ๋ CacheManager๋ฅผ ๋น์ผ๋ก ๋ฑ๋กํ๊ณ ์ํ๋ ๋ฉ์๋์ ์ด๋ ธํ ์ด์ ์ ๋ฌ๋ฉด ๋ค์ํ ์บ์ ์ ์ฅ์๋ฅผ ๋์ผํ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ฌ์ฉํ ์ ์๋ค.
๊ฐ๋จํ ์ฌ์ฉ ๋ฐฉ๋ฒ
Redis๋ฅผ ์ฌ์ฉํ๋ ๊ฐ๋จํ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์๊ฐํ๋ค. ๋จผ์ , Spring Data Redis์ ๋ํ ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
๊ทธ๋ฆฌ๊ณ ์๋์ ๊ฐ์ด Configuration์ Redis์ ์ฐ๊ฒฐ์ ์ํ RedisConnectionFactory์ RedisCacheManager์ ๋น์ ๋ฑ๋กํ๋ค.
@Configuration
@EnableCaching
public class CacheConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port),
LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(10))
.shutdownTimeout(Duration.ofSeconds(30))
.build());
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new JdkSerializationRedisSerializer()));
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
.withCacheConfiguration("postByAuthor",
redisCacheConfiguration.entryTtl(Duration.ofSeconds((long) 60 * 60 * 24)))
.build();
}
}
์บ์ฑ์ ์ ์ฉํ๊ณ ์ ํ๋ ๋ฉ์๋์ @Cacheable ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด ์บ์ ์ ์ฅ์์ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด ๊ฐ์ ธ์ ๋ฐํํ๊ณ , ๋ง์ฝ ์บ์ ์ ์ฅ์์ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด ๋ฉ์๋ ์๋ณธ ๋ก์ง์ ์ํํ ํ ๊ฒฐ๊ณผ๊ฐ์ ์บ์์ ์ ์ฅํ๊ณ ๋ฐํํ๊ฒ ๋ ๊ฒ์ด๋ค.
@Cacheable(cacheManager = "redisCacheManager", cacheNames = "postByAuthor")
public List<Post> getPostsByAuthor(Long authorId) {
List<Post> list = postJpaRepository.findAllByAuthorId(authorId);
return list.isEmpty() ? null : list;
}
์ ๊ณตํ๋ ์ด๋ ธํ ์ด์
์คํ๋ง ํ๋ ์์ํฌ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋์ ๊ฐ์ ๋ค์ํ ์ด๋ ธํ ์ด์ ์ ์ ๊ณตํ๋ค.
@EnableCaching
- Spring Cache์ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๊ธฐ ์ํด์ ์ค์ ํด๋์ค์ ์ด ์ด๋ ธํ ์ด์ ์ ๋ฌ์์ฃผ์ด์ผ ํ๋ค.
- ์ด ์ด๋
ธํ
์ด์
์ ๋ฌ ๋ CacheManager ๋น์ ํจ๊ป ๋ฑ๋กํด์ฃผ๋ฉด ๋๋ค.
- ์ธ๋ถ ์บ์ ์๋ฒ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ฌ๋ฌ ๊ฐ์ ์บ์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๋ ๋ฑ์ ์ถ๊ฐ ์ปค์คํฐ๋ง์ด์ง์ ์งํํ ๊ฒฝ์ฐ ์ค์ ํด๋์ค์ cacheManager ๋น์ ๋ฑ๋กํด์ผ ํ๋ค.
- ๋ฐ๋ก cacheManager๋ฅผ ๋น์ ๋ฑ๋กํ์ง ์์ผ๋ฉด ConcurrentHashMap ๊ธฐ๋ฐ ๋ก์ปฌ ์บ์ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค.
@Cacheable
- ๋ฉ์๋์ ๋ฐํ๊ฐ์ ์บ์ ์์ดํ ์ ์ ์ฅํ๋ค.
- ์บ์์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ฉด, ์บ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค. ๋ง์ฝ ์บ์์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด, ๋ฉ์๋ ๋ก์ง ์ํ ํ ๋ฐํ๊ฐ์ ์บ์์ ์ถ๊ฐํ๋ค.
๐จ ์ฃผ์
- key, keyGenerator ๋ ํจ๊ป ์ฐ์ผ ์ ์๋ค.
- cacheManager, cacheResolver๋ ํจ๊ป ์ฐ์ผ ์ ์๋ค.
Attribute Description
cacheNames | ์บ์ ์ด๋ฆ |
value | cacheNames์ Alias |
key | SpEL ํํ์์ ์์ฑํด ์บ์ ํค๋ฅผ ๋์ ์์ฑํ๋๋ก ํ๋ค. |
keyGenerator | ์ฌ์ฉํ KeyGenerator๋ฅผ ์ง์ ํ ์ ์๋ค. |
condition | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์๋ง ์บ์ฑ์ด ์ ์ฉ๋๋๋ก ํ๋ค. |
unless | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์ ์บ์ฑ์ด ์ ์ฉ๋์ง ์๋๋ก ํ๋ค. |
cacheManager | ์ฌ์ฉํ CacheManager๋ฅผ ์ง์ ํ ์ ์๋ค. |
cacheResolver | ์ฌ์ฉํ CacheResolver๋ฅผ ์ง์ ํ ์ ์๋ค. |
sync | ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์ผํ ํค์ ๋ํ ๊ฐ์ ๋ก๋ํ๋ ค๊ณ ํ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ ๋ฉ์๋์ ํธ์ถ์ ๋๊ธฐํ |
@CacheEvict
- ์ค๋๋๊ฑฐ๋ ์ฌ์ฉํ์ง ์๋ ๋ฐ์ดํฐ๋ฅผ ์ ๊ฑฐํ ๋, ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์บ์ ์์ดํ ์ ์ ๊ฑฐํ๊ธฐ ์ํด ํ์ฉํ๋ค.
- ๋ฉ์๋๊ฐ ํธ๋ฆฌ๊ฑฐ๋ก ๋์ํ๋ฏ๋ก ๋ฐํ ๊ฐ์ด ๋ฌด์๋์ด void ๋ฉ์๋์๋ ์ฌ์ฉํ ์ ์๋ค.
Attribute Description
cacheNames | ์บ์ ์ด๋ฆ |
value | cacheNames์ Alias |
key | SpEL ํํ์์ ์์ฑํด ์บ์ ํค๋ฅผ ๋์ ์์ฑํ๋๋ก ํ๋ค. |
condition | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์๋ง ์บ์ฑ์ด ์ ์ฉ๋๋๋ก ํ๋ค. |
unless | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์ ์บ์ฑ์ด ์ ์ฉ๋์ง ์๋๋ก ํ๋ค. |
cacheManager | ์ฌ์ฉํ CacheManager๋ฅผ ์ง์ ํ ์ ์๋ค. |
cacheResolver | ์ฌ์ฉํ CacheResolver๋ฅผ ์ง์ ํ ์ ์๋ค. |
allEntries | ์บ์์ ์ ์ฅ๋ ๊ฐ์ ๋ชจ๋ ์ ๊ฑฐํ ์ง์ ๋ํ ์ฌ๋ถ |
beforeInvocation | ๋ฉ์๋ ์ํ ์ ์บ์ ๋ฐ์ดํฐ ์ ๊ฑฐ๋ฅผ ์ํํ ์ง์ ๋ํ ์ฌ๋ถ |
@CachePut
- ์บ์์ ๊ฐ์ ์ ์ฅํ๋ ์ฉ๋๋ก๋ง ์ฌ์ฉ๋๋ฉฐ, ํญ์ ๋ฉ์๋ ๋ก์ง์ ์ํํ๊ณ ์บ์ ์์ดํ ์ ์ ์ฅํ๋ค.
Attribute Description
cacheNames | ์บ์ ์ด๋ฆ |
value | cacheNames์ Alias |
key | SpEL ํํ์์ ์์ฑํด ์บ์ ํค๋ฅผ ๋์ ์์ฑํ๋๋ก ํ๋ค. |
condition | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์๋ง ์บ์ฑ์ด ์ ์ฉ๋๋๋ก ํ๋ค. |
unless | SpEL ํํ์์ผ๋ก ์กฐ๊ฑด์ ์์ฑํ์ฌ true์ผ ๊ฒฝ์ฐ์ ์บ์ฑ์ด ์ ์ฉ๋์ง ์๋๋ก ํ๋ค. |
cacheManager | ์ฌ์ฉํ CacheManager๋ฅผ ์ง์ ํ ์ ์๋ค. |
cacheResolver | ์ฌ์ฉํ CacheResolver๋ฅผ ์ง์ ํ ์ ์๋ค. |
@Caching
- ๋์ผํ ์บ์ ์ด๋ ธํ ์ด์ ์ ์ฌ๋ฌ ๊ฐ ์ ์ธํ๊ณ ์ถ์ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ค.
- ์กฐ๊ฑด์ด๋ ํค ํํ ๋ฐฉ์์ ๋ฐ๋ผ ์ฌ๋ฌ ๋์์ ์ํํด์ผ ํ๋ ๊ฒฝ์ฐ ์ ์ฉํ๋ค.
Attribute Description
cacheable | ์ฌ๋ฌ ๊ฐ์ Cacheable ์ด๋ ธํ ์ด์ ์ ๋ ฅ |
evict | ์ฌ๋ฌ ๊ฐ์ CacheEvict ์ด๋ ธํ ์ด์ ์ ๋ ฅ |
put | ์ฌ๋ฌ ๊ฐ์ CachePut ์ด๋ ธํ ์ด์ ์ ๋ ฅ |
@CacheConfig
- ํด๋์ค ์์ค์ ์ด๋ ธํ ์ด์ ์ผ๋ก cacheNames, KeyGenerator ๋ฑ์ ์ค์ ํ์ฌ ๊ณต์ ํ ์ ์๋๋ก ํ๋ค.
- ํด๋์ค ๋ด๋ถ ์บ์ ์ฐ์ฐ ์ ์ด ์ค์ ๊ฐ์ ๊ธฐ๋ณธ์ผ๋ก ๊ฐ์ง๊ฒ ๋๋ค.
- ๋ฉ์๋ ๋ ๋ฒจ์์ ์ฌ์ ์ํ๋ฉด ๋ฎ์ด์ฐ์ฌ์ง๋ค.
Attribute Description
cacheNames | ์บ์ ์ด๋ฆ |
keyGenerator | ์ฌ์ฉํ KeyGenerator๋ฅผ ์ง์ ํ ์ ์๋ค. |
cacheManager | ์ฌ์ฉํ CacheManager๋ฅผ ์ง์ ํ ์ ์๋ค. |
cacheResolver | ์ฌ์ฉํ CacheResolver๋ฅผ ์ง์ ํ ์ ์๋ค. |
๋ด๋ถ ๋์ ์๋ฆฌ
์ ์ฒด์ ์ธ ๋์์ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ์๋์ ๊ฐ๋ค. AOP์ ์ํด Cacheable์ด ๋ฌ๋ฆฐ ํด๋์ค๋ ํ๋ก์ ํด๋์ค๊ฐ ๋๊ณ , ์ด ํด๋์ค์ ์ํด CacheInterceptor์ invoke ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค. ์ด ๋ฉ์๋ ๋ด๋ถ์์ ์ค์ Cache Server์ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ํ ๋ค์ํ ํด๋์ค์ ๋ฉ์๋๊ฐ ํธ์ถ๋ ๊ฒ์ด๋ค.
Configuration์ผ๋ก ํ์ํ ๋น ๋ฑ๋ก
@EnableCaching ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด CachingConfigurationSelector ํด๋์ค๋ฅผ ํตํด Configuration์ด ๋ฑ๋ก๋๋ค. ์ด ๋ @EnableCaching ์์ ์ค์ ํ AdviceMode์ ๋ฐ๋ผ PROXY, ASPECTJ ์ค ํ๋๋ก ๋์ํ๊ธฐ ์ํด ๊ฐ๊ฐ Configuration ๋ชฉ๋ก์ด ์ ๊ณต๋๋ค.
@EnableCaching ์์๋ Spring AOP์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ PROXY ๋ฐฉ์์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ์ง๋ง ASPECTJ ๋ฐฉ์์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ๊ฐ๊ฐํ ์กด์ฌํ๋ค. ์๋ฅผ ๋ค์๋ฉด, Spring AOP๋ ํ๋ก์ ํด๋์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋๋ฐ ๊ฐ์ ํด๋์ค์ ๋ฉ์๋๋ฅผ ๋ด๋ถ์ ์ผ๋ก ํธ์ถํ ๊ฒฝ์ฐ ํ๋ก์ ํด๋์ค๋ฅผ ๊ฑฐ์น์ง ์๊ธฐ ๋๋ฌธ์ AOP ๋ก์ง์ด ์ ์ฉ๋์ง ์๋๋ค. ASPECTJ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด weaving ๋ฐฉ์์ผ๋ก ํด๋์คํ์ผ(.class) ์์ฒด์ AOP ๋ก์ง์ ์ ์ฉํ๋ฏ๋ก ์ด๋ฌํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค. weaving ๋ฐฉ์์๋ ์ํ ์์ ์ ๋ฐ๋ผ compile-time, post-compile, load-time๋ก ๋๋๋ค. ๋ณดํต post-compile ๋ฐฉ์์ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ weavingํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ฏ๋ก, ๋ด๋ถ ํธ์ถ ๋ฌธ์ ํด๊ฒฐ์ ์ํด์๋ ๋๋จธ์ง ๋ ๋ฐฉ์์ด ์ฌ์ฉ๋๋ค. ๋ง์ฝ compile-time์ weaving์ด ๋๋๋ก ํ๋ ค๋ฉด gradle, maven์์ AspectJ๋ฅผ ์ง์ํ๋ ๋ชจ๋์ ์ํฌํธํ๊ณ AdviceMode๋ฅผ ASPECJ๋ก ์ค์ ํ๋ฉด ๋๋ค. ํน์ load-time์ weaving์ด ๋๋๋ก ํ๋ ค๋ฉด AspectJ agent๋ฅผ ์คํ ์ต์ ์ผ๋ก ๋ฑ๋กํ๊ณ @EnableLoadTimeWeaving ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด ๋๋ค.
PROXY ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ๋ฑ๋ก๋๋ ProxyCachingConfiguration ํด๋์ค์ ๋ด๋ถ์์๋ ์๋์ ๊ฐ์ด ์ธ๊ฐ์ง ๋น์ ๋ฑ๋กํ๋ค.
- BeanFactoryCacheOperationSourceAdvisor
- Spring Cache์์ AOP๋ก ๋์ํ๊ธฐ ์ํด ํฌ์ธํธ์ปท๊ณผ ์ด๋๋ฐ์ด์ค๋ฅผ ๋ฑ๋กํ๋ ์ด๋๋ฐ์ด์ ์ด๋ค.
- @EnableCaching ์์ ์ค์ ํ ์ฌ๋ฌ ๊ฐ์ ์ด๋๋ฐ์ด์ค๊ฐ ํน์ ์กฐ์ธ ํฌ์ธํธ์ ์ ์ฉ๋ ๊ฒฝ์ฐ ์ด๋ค ์์๋ก ์ ์ฉํด์ผ ํ ์ง ์ง์ ํ๋ order ์์ฑ์ advisor ๋ฑ๋ก ์์ ์ฌ์ฉํ๋ค.
- AnnotationCacheOperationSource
- ํฌ์ธํธ์ปท์ผ๋ก ๋ฑ๋ก๋๋ CacheOperationSourcePointcut ํด๋์ค์์ Spring Cache ์ด๋ ธํ ์ด์ ์ด ๋ถ์ด์๋์ง ๊ฒ์ฌํ๊ธฐ ์ํด ๋ด๋ถ์ ์ผ๋ก ๊ฐ์ง๋ ํด๋์ค์ด๋ค.
- SpringCacheAnnotationParser๋ฅผ ํตํด ์ด๋ ธํ ์ด์ ์ ๊ฒ์ฌํ์ฌ AOP ์ ์ฉ ๋์์ธ์ง ํ์ธํ๋ค.
- ์ ๊ทผ ์ ์ด์์ ๊ด๊ณ์์ด ๋ชจ๋ ํ์ํ๋ค.
- CacheInterceptor
- ํฌ์ธํธ์ปท์ ์ ์ฉ๋ invoke ๋ฉ์๋๋ฅผ ์ ์ํ๋ค.
- ์ค์ง์ ์ธ ๋ก์ง์ด ์์ฑ๋ CacheAspectSupport ๋ฅผ ์์๋ฐ๊ธฐ ๋๋ฌธ์, ๊ตฌ์ฒด์ ์ธ ๋ก์ง์ ์์ ํด๋์ค์ธ CacheAspectSupport ํด๋์ค์๊ฒ ๋๊ฒจ ์ฒ๋ฆฌํ๋ค.
ProxyCachingConfiguration ๋ด๋ถ์์ ๋น์ ๋ฑ๋กํ๋ ์ฝ๋๋ ์๋์ ๊ฐ์ด ์์ฑ๋์ด ์๋ค.
@Bean(
name = {"org.springframework.cache.config.internalCacheAdvisor"}
)
@Role(2)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder((Integer)this.enableCaching.getNumber("order"));
}
return advisor;
}
@Bean
@Role(2)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource(false);
}
@Bean
@Role(2)
public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource);
return interceptor;
}
์ด๋ ธํ ์ด์ ๋์ ํ๋ฆ
Spring Cache ์ด๋ ธํ ์ด์ ์ด ๋ฌ๋ฆฐ ๋ฉ์๋๊ฐ ํธ์ถ๋๋ฉด, Spring AOP์ ์ํด ํ๋ก์ ํด๋์ค์์ CacheInterceptor#invoke๋ฅผ ํธ์ถํ๊ฒ ๋๋ค. CacheInterceptor๋ ์บ์ฑ ๋ก์ง์ ๋ด๋นํ๋ CacheAspectSupport#execute ๋ฅผ ํธ์ถํ์ฌ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ์ด๋ ธํ ์ด์ ์ ์ฒ๋ฆฌํ๋ค.
- CacheEvict(beforeInvoaction = true)
- Cacheable (get)
- ์๋ณธ ๋ฉ์๋ invoke
- Cacheable (save) / CachePut
- CacheEvict(beforeInvoaction = false)
์๋๋ CacheAspectSupport์ ์ฝ๋๋ก, 1~5์ ๊ณผ์ ์ด ์ค์ ๋ก ์ด๋ป๊ฒ ์ํ๋๋์ง ํ์ธํ ์ ์๋ค.
@Nullable
private Object execute(CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
if (contexts.isSynchronized()) {
// Special handling of synchronized invocation
return executeSynchronized(invoker, method, contexts);
}
// 1) beforeInvocation์ด true์ธ Evict ์ฐ์ฐ ์ํ
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached value matching the conditions
// 2) Cacheable์ ์ํ ์บ์ ์กฐํ ์ฐ์ฐ ์ํ
Object cacheHit = findCachedValue(invoker, method, contexts);
if (cacheHit == null || cacheHit instanceof Cache.ValueWrapper) {
// 3 ~ 5) ๋๋จธ์ง ์์
์ ์๋ ๋ฉ์๋ ๋ด๋ถ์์ ์ํ
return evaluate(cacheHit, invoker, method, contexts);
}
return cacheHit;
}
// ...
@Nullable
private Object evaluate(@Nullable Object cacheHit, CacheOperationInvoker invoker, Method method,
CacheOperationContexts contexts) {
// Re-invocation in reactive pipeline after late cache hit determination?
if (contexts.processed) {
return cacheHit;
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = unwrapCacheValue(cacheHit);
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 3) ์๋ณธ ๋ฉ์๋ ์ํ
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect puts from any @Cacheable miss, if no cached value is found
List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class), cacheValue, cachePutRequests);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
// 4) Cacheable์์ ์บ์ ์กฐํ์ ์คํจํ๊ฑฐ๋ CachePut ์ด๋
ธํ
์ด์
์ ์ํ ์บ์ ์ ์ฅ ์ฐ์ฐ ์ํ
Object returnOverride = cachePutRequest.apply(cacheValue);
if (returnOverride != null) {
returnValue = returnOverride;
}
}
// Process any late evictions
// 5) beforeInvocation์ด false์ธ Evict ์ฐ์ฐ ์ํ
Object returnOverride = processCacheEvicts(
contexts.get(CacheEvictOperation.class), false, returnValue);
if (returnOverride != null) {
returnValue = returnOverride;
}
// Mark as processed for re-invocation after late cache hit determination
contexts.processed = true;
return returnValue;
}
์บ์ ์์ฒญ
๊ทธ๋ ๋ค๋ฉด ์ด๋ ธํ ์ด์ ์ ํตํด ์ค์ ์บ์์ ์ด๋ป๊ฒ ์์ฒญ์ด ๋ค์ด๊ฐ๊น? ์ค์ ์บ์ ์์ฒญ์ ๋ณด๋ด๋ ๋ก์ง์ Cache ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ ์์ฑ๋์ด ์๋ค.
Redis์ ๊ฒฝ์ฐ์๋ Cache ์ธํฐํ์ด์ค์ ๊ตฌํ์ฒด์ธ RedisCache ํด๋์ค๋ฅผ ์ ๊ณตํ๋ค. ๊ฐ๋จํ๊ฒ put ๋ฉ์๋๋ง ์ดํด๋ณด๋ฉด, Spring Data Redis ๋ด๋ถ์ ์ผ๋ก ์บ์์ ์์ฒญ์ ๋ณด๋ด๋ CacheWriter์ ๋ฉ์๋๋ฅผ ํธ์ถํจ์ ํ์ธํ ์ ์๋ค. ๊ฒฐ๊ตญ Redis์ TCP ์์ฒญ์ผ๋ก set ๋ช ๋ น์ ๋ณด๋ด๋ ์์ ์ด ์ํ๋๋ค.
public class RedisCache extends AbstractValueAdaptingCache {
// ...
@Override
public void put(Object key, @Nullable Object value) {
Object cacheValue = processAndCheckValue(value);
byte[] binaryKey = createAndConvertCacheKey(key);
byte[] binaryValue = serializeCacheValue(cacheValue);
Duration timeToLive = getTimeToLive(key, value);
// CacheWriter๋ฅผ ํตํด Redis์ ์ฝ์
์์ฒญ์ ๋ณด๋ธ๋ค.
getCacheWriter().put(getName(), binaryKey, binaryValue, timeToLive);
}
// ...
}
์ค์ ์บ์ ์ ์ฅ์์ ์์ฒญ์ ๋ณด๋ด๋ Cache ๊ตฌํ์ฒด์ ๋ฉ์๋๋ CacheAspectSupport๊ฐ ๋ด๋ถ์ ์ผ๋ก ํธ์ถํด์ผ ํ ๊ฒ์ด๋ค. ์๋๋ findCachedValue ๋ฉ์๋ ๋ด๋ถ์ ์ผ๋ก ํธ์ถ๋๋ findInCaches๋ผ๋ ๋ด๋ถ ๋ฉ์๋๋ก, AbstractCacheInvoker์ doRetrieve ๋ฉ์๋ ํน์ doGet ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ ์๋ค. Cache ๊ตฌํ์ฒด์ ๋ฉ์๋๋ AbstractCacheInvoker์ doGet, doRetrieve, doPut, doClear ๊ฐ์ ๋ฉ์๋๋ค์์ ํธ์ถ๋๋ค.
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
// ...
private Object findInCaches(CacheOperationContext context, Object key,
CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
for (Cache cache : context.getCaches()) {
if (CompletableFuture.class.isAssignableFrom(context.getMethod().getReturnType())) {
CompletableFuture<?> result = doRetrieve(cache, key);
if (result != null) {
return result.exceptionally(ex -> {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return null;
}).thenCompose(value -> (CompletableFuture<?>) evaluate(
(value != null ? CompletableFuture.completedFuture(unwrapCacheValue(value)) : null),
invoker, method, contexts));
}
else {
continue;
}
}
if (this.reactiveCachingHandler != null) {
Object returnValue = this.reactiveCachingHandler.findInCaches(
context, cache, key, invoker, method, contexts);
if (returnValue != ReactiveCachingHandler.NOT_HANDLED) {
return returnValue;
}
}
// ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋๊ธฐ ๋ฐฉ์์ ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค.
Cache.ValueWrapper result = doGet(cache, key);
if (result != null) {
return result;
}
}
return null;
}
// ...
}
public abstract class AbstractCacheInvoker {
// ...
@Nullable
protected <T> T doGet(Cache cache, Object key, Callable<T> valueLoader) {
try {
// Cache ๊ตฌํ์ฒด์ ๋ฉ์๋ ํธ์ถ
return cache.get(key, valueLoader);
}
catch (Cache.ValueRetrievalException ex) {
throw ex;
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
try {
return valueLoader.call();
}
catch (Exception ex2) {
throw new RuntimeException(ex2);
}
}
}
// doPut, doClear ๋ฑ์ ๋ฉ์๋๊ฐ ์กด์ฌํ๋ค...
}
๋ง๋ฌด๋ฆฌ
์ง๊ธ๊น์ง Spring Cacheable์ ๋ด๋ถ ๋์ ์๋ฆฌ์ ๋ํด ์ดํด๋ณด์๋ค. Spring AOP๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ชจ๋ฅธ๋ค๋ฉด ๋๋ฒ๊น ํ ๋ ํค๋งฌ ์ ์๋ค. ๋น๋ก ๊ฐ๋จํ ์ฐ์ฐ๋ง ๊ฐ๋ฅํ์ง๋ง ์ด๋ ๊ฒ ์ถ์ํ๋์ด ์๋ ์ด๋ ธํ ์ด์ ๋๋ถ์ ์บ์ ์๋ฃจ์ ์ ๊ต์ฒดํ ๋ ์บ์ ์ ์ฉ ์ฝ๋๋ฅผ ํ๋ํ๋ ๋ณ๊ฒฝํ์ง ์๊ณ ๋น ์ค์ ๋ง ๋ณ๊ฒฝํ์ฌ ์ฝ๊ฒ ๊ต์ฒดํ ์ ์๋ค.
๋๊ธ