Loading...
Spring Framework Reference Documentation 7.0.2의 Declarative Annotation-based Caching의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
캐싱 선언을 위해 Spring의 caching 추상화는 다음과 같은 Java 어노테이션들을 제공합니다:
@Cacheable: 캐시 채우기를 트리거합니다.@CacheEvict: 캐시 제거를 트리거합니다.@CachePut: 메서드 실행에 간섭하지 않고 캐시를 업데이트합니다.@Caching: 메서드에 적용할 여러 캐시 작업들을 재그룹합니다.@CacheConfig: 클래스 레벨에서 일부 공통 캐시 관련 설정을 공유합니다.@Cacheable Annotation이름에서 알 수 있듯이, @Cacheable을 사용하여 캐시 가능(캐시할 수 있는)한 메서드들을 구분할 수 있습니다. 즉,
결과가 캐시에 저장되어 이후 동일한 인자들로 호출될 때 실제로 메서드를 호출할 필요 없이
캐시에 있는 값이 반환되는 메서드들입니다.
가장 단순한 형태에서, 어노테이션 선언은 다음 예제와 같이 어노테이션이 달린 메서드와 연관된 캐시의 이름을 요구합니다:
1@Cacheable("books") 2public Book findBook(ISBN isbn) {...}
위의 코드 조각에서 findBook 메서드는 books라는 이름의 캐시와 연관됩니다.
메서드가 호출될 때마다, 해당 호출이 이미 실행되었고 반복할 필요가 없는지 확인하기 위해
캐시가 먼저 확인됩니다.
대부분의 경우 하나의 캐시만 선언되지만, 어노테이션은 여러 개의 이름을 지정할 수 있게 해주어 둘 이상의 캐시를 사용할 수 있습니다. 이 경우, 메서드를 호출하기 전에 각 캐시가 확인되며, 하나라도 캐시 적중이 발생하면 그 값이 반환됩니다.
캐시된 메서드가 실제로 호출되지 않았더라도, 값을 포함하지 않는 다른 모든 캐시들도 업데이트됩니다.
다음 예제는 findBook 메서드에 여러 캐시와 함께 @Cacheable을 사용하는 방법을 보여줍니다:
1@Cacheable({"books", "isbns"}) 2public Book findBook(ISBN isbn) {...}
캐시는 본질적으로 키-값 저장소이므로, 캐시된 메서드의 각 호출은 캐시 접근을 위한
적절한 키로 변환되어야 합니다. 캐싱 추상화는 다음 알고리즘을 기반으로 하는
단순한 KeyGenerator를 사용합니다:
SimpleKey.EMPTY를 반환합니다.SimpleKey를 반환합니다.이 접근 방식은 인자들이 자연스러운 키를 가지고 있고 유효한 hashCode()와 equals() 메서드를
구현하는 한 대부분의 사용 사례에서 잘 동작합니다. 그렇지 않은 경우 전략을 변경해야 합니다.
다른 기본 키 생성기를 제공하려면
org.springframework.cache.interceptor.KeyGenerator 인터페이스를 구현해야 합니다.
기본 키 생성 전략은 Spring 4.0 릴리스와 함께 변경되었습니다. 이전 Spring 버전들은 여러 키 인자에 대해 인자의
equals()는 고려하지 않고hashCode()만 고려하는 키 생성 전략을 사용했습니다. 이는 예기치 않은 키 충돌을 유발할 수 있었습니다 (배경은 spring-framework#14870를 참조하십시오).
새로운
SimpleKeyGenerator는 이러한 시나리오에 대해 복합 키를 사용합니다. 이전 키 전략을 계속 사용하려면 deprecated된org.springframework.cache.interceptor.DefaultKeyGenerator클래스를 설정하거나 해시 기반의 커스텀KeyGenerator구현을 생성할 수 있습니다.
캐싱은 범용적이기 때문에, 대상 메서드들은 캐시 구조 위에 쉽게 매핑될 수 없는 다양한 시그니처를 가질 가능성이 큽니다. 이는 대상 메서드에 여러 인자가 있지만 그 중 일부만이 캐싱에 적합하고(나머지는 메서드 로직에서만 사용되는) 경우 명확해집니다.
다음 예제를 살펴보십시오:
1@Cacheable("books") 2public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
언뜻 보기에는 두 개의 boolean 인자가 책을 찾는 방식에는 영향을 주지만
캐시에는 쓸모가 없습니다. 더 나아가, 둘 중 하나만 중요하고 다른 하나는 중요하지 않다면
어떻게 해야 할까요?
이러한 경우를 위해 @Cacheable 어노테이션은 key 속성을 통해 키가 생성되는 방식을
지정할 수 있게 해줍니다. SpEL을 사용하여 관심 있는 인자(또는 그 중첩된 프로퍼티)를 선택하고,
연산을 수행하거나, 어떤 인터페이스도 구현하거나 코드를 작성할 필요 없이 임의의 메서드를
호출할 수도 있습니다.
이는 코드 베이스가 커질수록 메서드들의 시그니처가 상당히 달라지는 경향이 있기 때문에 기본 생성기보다 권장되는 접근 방식입니다. 기본 전략은 일부 메서드에서는 동작할 수 있지만 모든 메서드에서 동작하는 경우는 거의 없습니다.
다음 예제들은 다양한 SpEL 선언을 사용합니다(SpEL에 익숙하지 않다면, Spring Expression Language를 읽어보는 것이 좋습니다):
1@Cacheable(cacheNames="books", key="#isbn") 2public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 3 4@Cacheable(cacheNames="books", key="#isbn.rawNumber") 5public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) 6 7@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") 8public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
위 코드 조각들은 특정 인자, 그 인자의 프로퍼티, 혹은 임의의 (static) 메서드를 얼마나 쉽게 선택할 수 있는지를 보여줍니다.
키를 생성하는 알고리즘이 너무 구체적이거나 공유되어야 하는 경우, 작업에 커스텀
keyGenerator를 정의할 수 있습니다. 이를 위해 사용할 KeyGenerator 빈 구현의 이름을
지정하면 됩니다.
다음 예제는 이를 보여줍니다:
1@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator") 2public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
key와keyGenerator파라미터는 상호 배타적이며 둘 다를 지정한 작업은 예외를 발생시킵니다.
캐싱 추상화는 설정된 CacheManager를 사용하여 작업 레벨에서 정의된 캐시를
가져오는 단순한 CacheResolver를 사용합니다.
다른 기본 캐시 리졸버를 제공하려면
org.springframework.cache.interceptor.CacheResolver 인터페이스를 구현해야 합니다.
기본 캐시 리졸루션은 단일 CacheManager로 작업하고 복잡한 캐시 리졸루션 요구 사항이
없는 애플리케이션에 잘 맞습니다.
여러 캐시 매니저로 작업하는 애플리케이션의 경우, 다음 예제와 같이 각 작업에서 사용할
cacheManager를 설정할 수 있습니다:
1@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") // (1) 2public Book findBook(ISBN isbn) {...}
| 1 | anotherCacheManager 지정. |
또한 키 생성을 교체하는 것과 유사한 방식으로
CacheResolver 자체를 완전히 교체할 수 있습니다.
리졸루션은 각 캐시 작업마다 요청되며, 구현체가 런타임 인자를 기반으로 사용할 캐시를 실제로 리졸브할 수 있게 해줍니다.
다음 예제는 CacheResolver를 지정하는 방법을 보여줍니다:
1@Cacheable(cacheResolver="runtimeCacheResolver") // (1) 2public Book findBook(ISBN isbn) {...}
| 1 | CacheResolver 지정. |
Spring 4.1부터 캐시 어노테이션의
value속성은 더 이상 필수 사항이 아닙니다. 어노테이션의 내용과 관계없이 이 정보는CacheResolver가 제공할 수 있기 때문입니다.key와keyGenerator와 마찬가지로,cacheManager와cacheResolver파라미터는 상호 배타적이며 둘 다를 지정한 작업은 예외를 발생시킵니다. 커스텀CacheManager는CacheResolver구현에 의해 무시되기 때문입니다. 이는 아마도 원하는 동작이 아닐 것입니다.
멀티스레드 환경에서는 특정 작업이 동일한 인자에 대해 동시에 호출될 수 있습니다 (일반적으로 시작 시). 기본적으로 캐시 추상화는 어떤 것도 잠그지 않으며, 동일한 값이 여러 번 계산되어 캐싱의 목적이 무의미해질 수 있습니다.
이러한 특정 경우에, sync 속성을 사용하여 값이 계산되는 동안 캐시 엔트리를 잠그도록
하위 캐시 제공자에 지시할 수 있습니다. 그 결과, 하나의 스레드만 값 계산에 바쁘게 되고
나머지 스레드들은 엔트리가 캐시에 업데이트될 때까지 블록됩니다.
다음 예제는 sync
속성을 사용하는 방법을 보여줍니다:
1@Cacheable(cacheNames="foos", sync=true) // (1) 2public Foo executeExpensiveOperation(String id) {...}
| 1 | sync 속성 사용. |
이는 선택적 기능이며, 사용하는 캐시 라이브러리가 이를 지원하지 않을 수도 있습니다. 코어 프레임워크에서 제공하는 모든
CacheManager구현은 이를 지원합니다. 자세한 내용은 사용 중인 캐시 제공자의 문서를 참조하십시오.
6.1부터 캐시 어노테이션은 CompletableFuture와 리액티브 반환 타입을 고려하여
캐시 상호작용을 자동으로 적응합니다.
CompletableFuture를 반환하는 메서드의 경우, 해당 future가 완료될 때 생성된 객체가
캐시에 저장되며, 캐시 적중에 대한 캐시 조회는 CompletableFuture를 통해
검색됩니다:
1@Cacheable("books") 2public CompletableFuture<Book> findBook(ISBN isbn) {...}
Reactor Mono를 반환하는 메서드의 경우, 해당 Reactive Streams 퍼블리셔가 emit하는
객체는 사용 가능해지는 즉시 캐시에 저장되며, 캐시 적중에 대한 캐시 조회는
Mono(내부적으로는 CompletableFuture 기반)를 통해 검색됩니다:
1@Cacheable("books") 2public Mono<Book> findBook(ISBN isbn) {...}
Reactor Flux를 반환하는 메서드의 경우, 해당 Reactive Streams 퍼블리셔가 emit하는
객체들은 List로 수집되어 그 리스트가 완료되면 캐시에 저장되며, 캐시 적중에 대한
캐시 조회는 캐시된 List 값을 위한 CompletableFuture 기반의 Flux로
검색됩니다:
1@Cacheable("books") 2public Flux<Book> findBooks(String author) {...}
이러한 CompletableFuture 및 리액티브 적응은 동기화된 캐싱에도
동작하며, 동시 캐시 미스의 경우 값을 한 번만 계산합니다:
1@Cacheable(cacheNames="foos", sync=true) // (1) 2public CompletableFuture<Foo> executeExpensiveOperation(String id) {...}
| 1 | sync 속성 사용. |
이러한 구성이 런타임에서 동작하려면, 설정된 캐시는
CompletableFuture기반 조회를 지원해야 합니다. Spring이 제공하는ConcurrentMapCacheManager는 해당 조회 스타일에 자동으로 적응하며,CaffeineCacheManager는 비동기 캐시 모드가 활성화되면 이를 네이티브하게 지원합니다:CaffeineCacheManager인스턴스에서setAsyncCacheMode(true)를 설정하십시오.
1@Bean 2CacheManager cacheManager() { 3 CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 4 cacheManager.setCacheSpecification(...); 5 cacheManager.setAsyncCacheMode(true); 6 return cacheManager; 7}
마지막으로, 어노테이션 기반 캐싱은 composition 및 back pressure를 포함하는 정교한 리액티브 상호작용에는 적합하지 않다는 점에 유의하십시오.
특정 리액티브 메서드에 @Cacheable을 선언하기로 선택했다면, 단순히 Mono의 emit된
객체 또는 Flux의 사전 수집된 객체 리스트를 저장하는 다소 조잡한 캐시 상호작용이
미치는 영향을 고려해야 합니다.
때때로 메서드는 항상 캐싱에 적합하지 않을 수 있습니다(예를 들어, 주어진 인자에
의존하는 경우). 캐시 어노테이션은 true 또는 false로 평가되는 SpEL 표현식을
받는 condition 파라미터를 통해 이러한 사용 사례를 지원합니다.
true이면 메서드가
캐시됩니다. 그렇지 않으면 메서드는 캐시되지 않은 것처럼 동작합니다(즉, 캐시에 어떤
값이 있거나 어떤 인자가 사용되더라도 메서드는 매번 호출됩니다).
예를 들어, 다음
메서드는 인자 name의 길이가 32보다 짧은 경우에만 캐시됩니다:
1@Cacheable(cacheNames="book", condition="#name.length() < 32") // (1) 2public Book findBook(String name)
| 1 | @Cacheable에 condition 설정. |
condition 파라미터 외에도, 값을 캐시에 추가하는 것을 거부하기 위해 unless 파라미터를
사용할 수 있습니다. condition과 달리 unless 표현식은 메서드가 호출된 후에
평가됩니다.
이전 예제를 확장하여, 다음 예제와 같이 paperback 책만 캐시하고 싶을 수 있습니다:
1@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") // (1) 2public Book findBook(String name)
| 1 | hardback을 차단하기 위해 unless 속성 사용. |
캐시 추상화는 java.util.Optional 반환 타입을 지원합니다. Optional 값이
present 상태이면, 해당 값이 연관된 캐시에 저장됩니다. Optional 값이 present가
아니면, null이 연관된 캐시에 저장됩니다.
#result는 항상 비즈니스 엔티티를 가리키며,
래퍼를 가리키지 않으므로, 이전 예제는 다음과 같이 다시 작성할 수 있습니다:
1@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback") 2public Optional<Book> findBook(String name)
#result는 여전히 Optional<Book>가 아니라 Book을 가리킨다는 점에 유의하십시오.
null일 수 있으므로 SpEL의 safe navigation 연산자를 사용합니다.
각 SpEL 표현식은 전용 context를 기반으로 평가됩니다.
내장 파라미터 외에도, 프레임워크는 인자 이름과 같은 전용 캐싱 관련 메타데이터를 제공합니다.
다음 표는 키 및 condition 계산에 사용할 수 있도록 context에 제공되는 항목들을 설명합니다:
| Name | Location | Description | Example |
|---|---|---|---|
methodName | Root object | 호출되는 메서드의 이름 | #root.methodName |
method | Root object | 호출되는 메서드 | #root.method.name |
target | Root object | 호출되는 대상 객체 | #root.target |
targetClass | Root object | 호출되는 대상의 클래스 | #root.targetClass |
args | Root object | 대상 호출에 사용되는 인자들(object 배열) | #root.args[0] |
caches | Root object | 현재 메서드가 실행되는 캐시들의 컬렉션 | #root.caches[0].name |
| Argument name | Evaluation context | 특정 메서드 인자의 이름. 이름을 사용할 수 없는 경우<br>(예: 코드가 -parameters 플래그 없이 컴파일된 경우), 개별 인자는 #a<#arg> 구문을<br>사용하여도 사용할 수 있습니다. 여기서 <#arg>는 인자 인덱스(0부터 시작)를 의미합니다. | #iban 또는 #a0 (#p0 또는 #p<#arg> 표기법을 별칭으로 사용할 수도 있습니다). |
result | Evaluation context | 메서드 호출의 결과(캐시될 값). unless 표현식, cache put 표현식<br>(key 계산용), 또는 beforeInvocation이 false인 cache evict 표현식에서만<br>사용할 수 있습니다. 지원되는 래퍼(Optional 등)의 경우 #result는 래퍼가 아닌<br>실제 객체를 가리킵니다. | #result |
Table 1. Cache metadata available in SpEL expressions
@CachePut Annotation캐시를 업데이트할 필요는 있지만 메서드 실행에 간섭하고 싶지 않을 때는
@CachePut 어노테이션을 사용할 수 있습니다. 즉, 메서드는 항상 호출되고 그 결과는
(@CachePut 옵션에 따라) 캐시에 저장됩니다.
이는 @Cacheable과 동일한 옵션을
지원하며, 메서드 흐름 최적화가 아닌 캐시 채우기에 사용해야 합니다.
다음 예제는 @CachePut 어노테이션을 사용합니다:
1@CachePut(cacheNames="book", key="#isbn") 2public Book updateBook(ISBN isbn, BookDescriptor descriptor)
동일한 메서드에
@CachePut과@Cacheable어노테이션을 함께 사용하는 것은 일반적으로 강력히 권장되지 않습니다. 두 어노테이션은 서로 다른 동작을 가지기 때문입니다. 후자는 캐시를 사용하여 메서드 호출을 건너뛰는 반면, 전자는 캐시 업데이트를 실행하기 위해 호출을 강제합니다. 이는 예기치 않은 동작을 초래하며, 특정 코너 케이스(예: 서로를 제외하는 condition을 가진 어노테이션) 이외에는 이러한 선언을 피해야 합니다. 또한 이러한 condition은 결과 객체 (즉,#result변수)에 의존해서는 안 됩니다. 이들은 제외를 확인하기 위해 사전에 검증되기 때문입니다.
6.1부터 @CachePut은 CompletableFuture 및 리액티브 반환 타입을 고려하여,
생성된 객체가 사용 가능해지는 즉시 put 작업을 수행합니다.
@CacheEvict Annotation캐시 추상화는 캐시 저장소의 채우기뿐 아니라 제거도 허용합니다. 이 과정은 캐시에서 오래되었거나 사용되지 않는 데이터를 제거하는 데 유용합니다.
@Cacheable과 달리, @CacheEvict는 캐시 제거(즉, 캐시에서 데이터를 제거하는
트리거 역할을 하는 메서드)를 수행하는 메서드를 구분합니다.
@CacheEvict는 그 형제와 유사하게, 작업의 영향을 받는 하나 이상의 캐시 지정을
요구하며, 커스텀 캐시 및 키 리졸루션 또는 condition을 지정할 수 있게 해주고,
엔트리 제거가 아닌 캐시 전체 제거 수행 여부를 나타내는 추가 파라미터
(allEntries)를 제공합니다.
다음 예제는 books 캐시에서 모든 엔트리를 제거합니다:
1@CacheEvict(cacheNames="books", allEntries=true) // (1) 2public void loadBooks(InputStream batch)
| 1 | 캐시에서 모든 엔트리를 제거하기 위해 allEntries 속성 사용. |
이 옵션은 전체 캐시 리전을 비워야 할 때 유용합니다. 각 엔트리를 개별적으로 제거하는 대신(이는 비효율적이어서 오랜 시간이 걸립니다), 위 예제에서 보듯이 모든 엔트리가 한 번의 작업으로 제거됩니다.
이 시나리오에서는 프레임워크가 어떤 키도 무시한다는 점에 유의하십시오. 키는 적용되지 않기 때문입니다(전체 캐시가 제거되며, 단일 엔트리만 제거되는 것이 아닙니다).
또한 beforeInvocation 속성을 사용하여 제거가 메서드가 호출된 후(기본값) 또는
호출되기 전에 발생해야 하는지 지정할 수 있습니다. 전자는 나머지 어노테이션과 동일한
시맨틱을 제공합니다. 메서드가 성공적으로 완료되면 캐시에서 작업(이 경우 제거)이
실행됩니다. 메서드가 실행되지 않거나(캐시될 수 있기 때문에) 예외가 발생하면
제거는 발생하지 않습니다.
후자(beforeInvocation=true)는 제거가 항상 메서드가
호출되기 전에 발생하도록 합니다. 이는 제거가 메서드 결과에 연결될 필요가 없는 경우에
유용합니다.
void 메서드는 @CacheEvict와 함께 사용할 수 있다는 점에 유의하십시오. 이 메서드들은
트리거 역할을 하므로, 반환 값은 무시됩니다(캐시와 상호작용하지 않기 때문입니다).
이는 캐시에 데이터를 추가하거나 캐시의 데이터를 업데이트하는 @Cacheable과는
다른 점이며, 따라서 결과가 필요합니다.
6.1부터 @CacheEvict은 CompletableFuture 및 리액티브 반환 타입을 고려하여,
처리가 완료되면 호출 이후 제거 작업을 수행합니다.
@Caching Annotation때때로 @CacheEvict 또는 @CachePut과 같이 동일한 타입의 여러 어노테이션을
지정해야 할 때가 있습니다. 예를 들어, 서로 다른 캐시 간에 condition이나 키
표현식이 다른 경우입니다.
@Caching을 사용하면 동일한 메서드에서 여러 개의
중첩된 @Cacheable, @CachePut, 및 @CacheEvict 어노테이션을 사용할 수 있습니다.
다음 예제는 두 개의 @CacheEvict 어노테이션을 사용합니다:
1@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) 2public Book importBooks(String deposit, Date date)
@CacheConfig Annotation지금까지 캐싱 작업이 많은 커스터마이징 옵션을 제공하며 이러한 옵션을 각 작업마다 설정할 수 있음을 확인했습니다. 그러나 일부 커스터마이징 옵션은 클래스의 모든 작업에 적용되는 경우 설정이 번거로울 수 있습니다.
예를 들어, 클래스의 모든 캐시 작업에 사용할 캐시 이름을 지정하는 작업은
단일 클래스 레벨 정의로 대체할 수 있습니다. 여기서 @CacheConfig가 등장합니다.
다음 예제는 @CacheConfig를 사용하여 캐시 이름을 설정합니다:
1@CacheConfig("books") // (1) 2public class BookRepositoryImpl implements BookRepository { 3 4 @Cacheable 5 public Book findBook(ISBN isbn) {...} 6}
| 1 | 캐시 이름을 설정하기 위해 @CacheConfig 사용. |
@CacheConfig는 캐시 이름, 커스텀 KeyGenerator, 커스텀 CacheManager, 그리고
커스텀 CacheResolver를 공유할 수 있게 해주는 클래스 레벨 어노테이션입니다.
이 어노테이션을 클래스에 배치하는 것만으로는 어떤 캐싱 작업도 활성화되지
않습니다.
작업 레벨 커스터마이징은 항상 @CacheConfig에 설정된 커스터마이징을
오버라이드합니다. 따라서 각 캐시 작업에는 세 수준의 커스터마이징이 존재합니다:
CachingConfigurer를 통해 전역적으로 설정된 커스터마이징: 다음 섹션 참조.@CacheConfig를 사용한 클래스 레벨 커스터마이징.제공자별 설정은 일반적으로
CaffeineCacheManager와 같은CacheManager빈에서 제공됩니다. 이는 사실상 전역 설정이기도 합니다.
캐시 어노테이션을 선언하는 것만으로는 그 작업이 자동으로 트리거되지 않는다는 점에 유의하는 것이 중요합니다. Spring의 많은 기능과 마찬가지로, 이 기능은 선언적으로 활성화해야 합니다(즉, 캐싱이 문제의 원인이라고 의심되는 경우 코드의 모든 어노테이션을 제거하는 대신 설정 라인 한 줄만 제거하여 비활성화할 수 있습니다).
캐싱 어노테이션을 활성화하려면 @Configuration 클래스 중 하나에 @EnableCaching
어노테이션을 추가하거나 XML에서 cache:annotation-driven 요소를 사용하십시오:
1@Configuration 2@EnableCaching 3class CacheConfiguration { 4 5 @Bean 6 CacheManager cacheManager() { 7 CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 8 cacheManager.setCacheSpecification("..."); 9 return cacheManager; 10 } 11}
1@Configuration 2@EnableCaching 3class CacheConfiguration { 4 5 @Bean 6 fun cacheManager(): CacheManager { 7 return CaffeineCacheManager().apply { 8 setCacheSpecification("...") 9 } 10 } 11}
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:cache="http://www.springframework.org/schema/cache" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd 6 http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache.xsd"> 7 8 <cache:annotation-driven/> 9 10 <bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager"> 11 <property name="cacheSpecification" value="..."/> 12 </bean> 13</beans>
cache:annotation-driven 요소와 @EnableCaching 어노테이션은 둘 다 AOP를 통해
애플리케이션에 캐싱 동작이 추가되는 방식을 제어하는 다양한 옵션을 지정할 수 있게
해줍니다. 이 설정은 @Transactional과 의도적으로 유사합니다.
캐싱 어노테이션을 처리하기 위한 기본 어드바이스 모드는
proxy이며, 프록시를 통한 호출만 가로챌 수 있습니다. 동일한 클래스 내의 로컬 호출은 이 방식으로 가로챌 수 없습니다. 더 발전된 가로채기 모드가 필요하다면, 컴파일 타임 또는 로드 타임 위빙과 함께aspectj모드로 전환하는 것을 고려하십시오.
(Java 설정을 사용한) 고급 커스터마이징에 대한 자세한 내용은, 구현이 필요한
CachingConfigurer의 javadoc을 참조하십시오.
| XML Attribute | Annotation Attribute | Default | Description |
|---|---|---|---|
cache-manager | N/A (see the CachingConfigurer javadoc) | cacheManager | 사용할 캐시 매니저의 이름. 기본 CacheResolver는 이 캐시 매니저(또는 설정되지<br>않은 경우 cacheManager)로 내부에서 초기화됩니다. 캐시 리졸루션을 보다 세밀하게<br>관리하려면 'cache-resolver' 속성 설정을 고려하십시오. |
cache-resolver | N/A (see the CachingConfigurer javadoc) | 설정된 cacheManager를 사용하는 SimpleCacheResolver | 백킹 캐시를 리졸브하는 데 사용할 CacheResolver의 빈 이름. 이 속성은 필수 사항이<br>아니며, 'cache-manager' 속성의 대안으로 지정해야 할 때만 필요합니다. |
key-generator | N/A (see the CachingConfigurer javadoc) | SimpleKeyGenerator | 사용할 커스텀 키 생성기의 이름. |
error-handler | N/A (see the CachingConfigurer javadoc) | SimpleCacheErrorHandler | 사용할 커스텀 캐시 에러 핸들러의 이름. 기본적으로 캐시 관련 작업 중 발생한<br>예외는 클라이언트에 그대로 다시 던져집니다. |
mode | mode | proxy | 기본 모드(proxy)는 Spring의 AOP 프레임워크를 사용하여 어노테이션이 달린 빈을 프록시로<br>처리합니다(앞서 논의한 프록시 시맨틱을 따르며, 프록시를 통해 들어오는 메서드 호출에만 적용됩니다).<br>대체 모드(aspectj)는 대신 Spring의 AspectJ 캐싱 aspect로 영향을 받는 클래스를 위빙하여,<br>대상 클래스 바이트 코드를 수정하고 모든 종류의 메서드 호출에 적용합니다. AspectJ 위빙에는<br>클래스패스에 spring-aspects.jar가 필요하고 로드 타임 위빙(또는 컴파일 타임 위빙)이<br>활성화되어야 합니다. (로드 타임 위빙 설정 방법에 대한 자세한 내용은 |
Spring configuration을 참조하십시오.) | |
proxy-target-class|proxyTargetClass|false| 프록시 모드에만 적용됩니다.@Cacheable또는@CacheEvict어노테이션이 달린 클래스에 대해<br>어떤 타입의 캐싱 프록시가 생성될지를 제어합니다.proxy-target-class속성이true로<br>설정되면 클래스 기반 프록시가 생성됩니다.proxy-target-class가false이거나 속성이<br>생략되면 표준 JDK 인터페이스 기반 프록시가 생성됩니다. (서로 다른 프록시 타입에 대한 자세한<br>설명은 Proxying Mechanisms을 참조하십시오.) | |order|order| Ordered.LOWEST_PRECEDENCE |@Cacheable또는@CacheEvict어노테이션이 달린 빈에 적용되는 캐시 어드바이스의 순서를<br>정의합니다. (AOP 어드바이스 순서와 관련된 규칙에 대한 자세한 내용은 Advice Ordering을 참조하십시오.)<br>순서가 지정되지 않은 경우 AOP 서브시스템이 어드바이스 순서를 결정합니다. |
Table 2. Cache annotation settings
<cache:annotation-driven/>는 자신이 정의된 동일한 애플리케이션 컨텍스트 내의 빈에서만@Cacheable/@CachePut/@CacheEvict/@Caching을 찾습니다. 즉,DispatcherServlet용WebApplicationContext에<cache:annotation-driven/>를 배치하면 컨트롤러의 빈만 확인하고 서비스는 확인하지 않습니다. 자세한 내용은 the MVC section을 참조하십시오.
프록시를 사용할 때는 캐시 어노테이션을 public 가시성을 가진 메서드에만 적용해야 합니다. protected, private, 또는 패키지 가시성 메서드에 이러한 어노테이션을 달더라도 에러는 발생하지 않지만, 어노테이션이 달린 메서드는 설정된 캐싱 설정을 나타내지 않습니다.
non-public 메서드에 어노테이션을 달아야 하는 경우, 바이트코드 자체를 변경하는 AspectJ 사용을 고려하십시오(이 섹션의 나머지 부분 참조).
Spring은 인터페이스에 어노테이션을 다는 것보다
@Cache*어노테이션을 구체 클래스(및 구체 클래스의 메서드)에만 다는 것을 권장합니다. 인터페이스(또는 인터페이스 메서드)에@Cache*어노테이션을 배치할 수는 있지만, 이는 프록시 모드(mode="proxy")를 사용하는 경우에만 동작합니다. 위빙 기반 aspect(mode="aspectj")를 사용하는 경우, 캐싱 설정은 위빙 인프라스트럭처에 의해 인터페이스 레벨 선언에서 인식되지 않습니다.
프록시 모드(기본값)에서는 프록시를 통해 들어오는 external 메서드 호출만 가로채집니다. 즉, self-invocation(실제로는 대상 객체 내의 메서드가 같은 대상 객체의 다른 메서드를 호출하는 경우)은 호출된 메서드에
@Cacheable이 표시되어 있더라도 런타임에서 실제 캐싱으로 이어지지 않습니다. 이 경우aspectj모드를 사용하는 것을 고려하십시오. 또한 기대한 동작을 제공하려면 프록시가 완전히 초기화되어야 하므로, 초기화 코드(예:@PostConstruct)에서 이 기능에 의존해서는 안 됩니다.
이 기능은 프록시 기반 접근 방식에서만 동작하지만, 약간의 추가 작업을 통해 AspectJ를 사용하여 활성화할 수 있습니다.
spring-aspects 모듈은 표준 어노테이션만을 위한 aspect를 정의합니다.
자신만의 어노테이션을 정의한 경우, 그 어노테이션을 위한 aspect도 정의해야 합니다.
예시는 AnnotationCacheAspect를 확인하십시오.
캐싱 추상화는 어떤 메서드가 캐시 채우기 또는 제거를 트리거하는지를
식별하기 위해 자체 어노테이션을 사용할 수 있게 해줍니다. 이는 템플릿 메커니즘으로
매우 유용한데, 특히 키나 condition이 지정되었거나 코드 베이스에서 외부 import
(org.springframework)가 허용되지 않는 경우 캐시 어노테이션 선언을 중복할 필요를
제거해 줍니다.
다른 stereotype 어노테이션과 마찬가지로,
@Cacheable, @CachePut, @CacheEvict, 및 @CacheConfig를
메타 어노테이션(다른 어노테이션을 annotate할 수 있는 어노테이션)으로 사용할 수 있습니다.
다음 예제에서는 일반적인 @Cacheable 선언을 자체 커스텀 어노테이션으로 대체합니다:
1@Retention(RetentionPolicy.RUNTIME) 2@Target({ElementType.METHOD}) 3@Cacheable(cacheNames="books", key="#isbn") 4public @interface SlowService { 5}
위 예제에서는 @Cacheable로 자체 어노테이션을 annotate한 SlowService 어노테이션을
정의했습니다. 이제 다음 코드를 대체할 수 있습니다:
1@Cacheable(cacheNames="books", key="#isbn") 2public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
다음 예제는 위 코드를 대체할 수 있는 커스텀 어노테이션을 보여줍니다:
1@SlowService 2public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@SlowService는 Spring 어노테이션이 아니지만, 컨테이너는 런타임에 해당 선언을 자동으로
인식하고 그 의미를 이해합니다. 앞서 언급했듯이,
earlier,
어노테이션 기반 동작은 활성화되어야 한다는 점에 유의하십시오.
Understanding the Cache Abstraction
JCache (JSR-107) Annotations