📘 Backend/Spring

Spring ehCache & DevTools

신건우 2023. 9. 28. 18:32

📘 Spring ehCache

Spring에서 간단하게 사용할 수 있는 Java기반 오픈소스 캐시 라이브러리 입니다.

redis나 memcached같은 캐시 엔진들도 있지만,

저 2개의 캐시 엔진과는 달리 ehcache는 Daemon을 가지지 않고 Spring 내부적으로 동작하여 캐싱 처리를 합니다.


따라서 redis같이 별도의 서버를 사용하여 생길 수 있는 네트워크 지연 혹은 단절같은 이슈에서 자유롭고,

같은 로컬 환경 일지라도 별도로 구동하는 memcached와는 다르게 ehcache는 서버 어플리케이션과 라이프사이클을 같이 하므로 사용하기 더욱 간편합니다.


Ehcache의 2.x 버전과 3 버전의 차이는 큽니다.

3 버전 부터는 javax.cache API(JSR-107)와의 호환성을 제공합니다. 따라서 표준을 기반으로 만들어 졌습니다.

또, 3 버전에서는 offheap이라는 저장 공간을 제공합니다.

offheap이란 말 그대로 힙 메모리를 벗어난 메모리로 Java GC에 의해 데이터가 정리되지 않는 공간입니다.


3 버전을 기준으로 글을 작성하겠습니다.


📘 사용법 - 설정

Ehcache를 사용하는 방법엔 여러가지가 있습니다.

  • Github에서 직접 jar를 받아 import 하기
  • Maven Central 에서 Dependency 가져오기

Maven Dependency로 가져오는 방법을 선택하겠습니다.


build.gradle

implementation 'org.springframework.boot:spring-boot-starter-cache'  
implementation 'org.ehcache:ehcache:3.10.8'  
implementation 'javax.cache:cache-api:1.1.1' // expiry 기능을 위해 필요 (JSR-107 API)

ehcache.xml

이제 캐시에 대해 어떻게 처리할 것인지 정의하기 위해 ehcache.xml파일을 작성합니다.

파일의 위치는 프로젝트 내 resources 디렉터리 하위에 위치해야 합니다.


config : Ehcache 구성 파일의 최상위 요소입니다.

네임스페이스와 스키마 위치 등을 정의합니다.


cache-template : 캐시 템플릿을 정의하는 요소입니다.

여러 개의 캐시에서 공통으로 사용할 수 있는 설정들을 포함합니다.

  • expiry: 캐시 항목 만료 시간(TTL)을 설정하는 요소입니다.
  • listeners: 이벤트 리스너를 정의하는 요소입니다. 지정된 이벤트가 발생할 때 호출될 클래스와 실행 모드 등을 설정할 수 있습니다.

cache : 실제로 사용되는 개별 캐시를 정의하는 요소입니다.

  • alias: 캐시에 대한 별칭(이름)을 지정합니다.
  • uses-template: 해당 템플릿에서 상속 받은 설정들을 사용합니다.
  • key-typevalue-type: 저장되는 항목(key-value pair)의 데이터 타입을 지정합니다.
  • expiry: 개별적으로 설정된 TTL 값을 가집니다.
  • resources: 메모리 리소스 관련 설정들이 포함됩니다. 예제에서는 Off-Heap 메모리 크기를 10MB로 지정하였습니다.

Listener : 이벤트 리스너를 정의하는 요소입니다.

지정된 이벤트가 발생할 때 호출될 클래스와 실행 모드 등을 설정할 수 있습니다.

캐시가 생성되고 삭제되고 하는 이벤트를 모니터링 하고 싶으면 org.ehcache.event.CacheEventListener 를 구현하는 클래스를 만들어서 설정합니다. (태그 순서가 중요)


<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xmlns="http://www.ehcache.org/v3"  
  xsi:schemaLocation="http://www.ehcache.org/v3  
  http://www.ehcache.org/schema/ehcache-core-3.10.xsd">  

<!--    <persistence directory="${java.io.tmpdir}"/>-->  
  <!--  <cache-template name="template">-->   
  <!--    <listeners>-->  
  <!--        <listener>-->  
  <!--            <class>sample.CacheEventLogger</class>-->  
  <!--            <event-firing-mode>ASYNCHRONOUS</event-firing-mode>--> 
  <!--            <event-ordering-mode>UNORDERED</event-ordering-mode>-->  
  <!--            <events-to-fire-on>CREATED</events-to-fire-on>-->  
  <!--            <events-to-fire-on>EVICTED</events-to-fire-on>-->  
  <!--            <events-to-fire-on>REMOVED</events-to-fire-on>-->  
  <!--            <events-to-fire-on>UPDATED</events-to-fire-on>-->  
  <!--            <events-to-fire-on>EXPIRED</events-to-fire-on>--> 
  <!--        </listener>-->  
  <!--    </listeners>-->  
  <!--  </cache-template>-->

  <cache-template name="defaultTemplate">  
    <expiry>  
      <ttl unit="seconds">600</ttl>  
    </expiry>  
    <listeners>  
        <listener>  
            <class>{프로젝트 패키직 경로 + class 명}</class>  
            <event-firing-mode>ASYNCHRONOUS</event-firing-mode>  
            <event-ordering-mode>UNORDERED</event-ordering-mode>  
            <events-to-fire-on>CREATED</events-to-fire-on>  
            <events-to-fire-on>EVICTED</events-to-fire-on>  
            <events-to-fire-on>REMOVED</events-to-fire-on>  
            <events-to-fire-on>UPDATED</events-to-fire-on>  
            <events-to-fire-on>EXPIRED</events-to-fire-on>  
        </listener>  
    </listeners>  
  </cache-template>  

  <cache alias="msgCache" uses-template="defaultTemplate">  
    <key-type>java.lang.String</key-type>  
    <value-type>java.lang.String</value-type>  

    <expiry>  
      <!-- 캐시 만료 시간 = timeToLiveSeconds -->      
      <ttl unit="seconds">30</ttl>  
    </expiry>  

    <resources>  
      <!-- JVM heap 메모리 외부의 메모리 -->  
      <offheap unit="MB">10</offheap>  
      <!-- Disk 메모리, LFU strategy-->  
      <!--      persistent="false" Ehcache will wipe the disk data on shutdown.-->      
      <!--      persistent="true" Ehcache will preserve the disk data on shutdown and try to load it back on restart of the JVM.-->      
      <!--      <disk unit="MB" persistent="false">10</disk>-->    
      </resources>  
  </cache>  
</config>

application.yml

spring:
    cache:
        jcache:
            config: classpath:ehcache.xml

Main Application

@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Applicaton.class, args);
    }
}

EhcacheEventLogging

이벤트 발생 시 로그를 생성하는 클래스입니다.

캐싱 처리가 되면 이 클래스 내부의 onEvent() 함수가 호출되면서 로그가 찍힐겁니다.

@Slf4j  
public class EhcacheEventLogging implements CacheEventListener<Object, Object> {  

  @Override  
  public void onEvent(CacheEvent<?, ?> cacheEvent) {  
    log.debug("Key:[{}]|EventType:[{}]|OLD:[{}]|New:[{}]",  
        cacheEvent.getKey(), cacheEvent.getType(), cacheEvent.getOldValue(),  
        cacheEvent.getNewValue());  
  }   
}

📘 Caching 적용

캐싱 하려는 함수에 @Cacheable 어노테이션을 달아줍니다.

@Cacheable의 인자로 value, key, condition을 지정할 수 있습니다.

  • value : ehcache.xml에서 등록했던 캐시 중 함수에 적용할 캐시의 이름 (alias 명)
  • key : 캐시를 구분하기 위한 용도, 만약 구분할 필요가 없으면 key는 생략해도 됩니다.
  • condition : 캐리 처리에 대한 조건 지정, 예를 들어 ""#number > 10"로 지정하면 파라미터인 number가 10 보다 큰 경우만 캐리 처리를 한다는 의미입니다.
@Cacheable(value = "squareCache", key = "#number", condition = "#number > 10")
public BigDecimal square(Long number) {
    BigDecimal square = BigDecimal.valueOf(number).multiply(BigDecimal.valueOf(number));
    log.info("square of {} is {}", number, square);
    return square;
}

참고

ehcache3 는 캐싱할 데이터를 외부 메모리(offheap 혹은 disk)에 저장하기 위해서는,

저장할 데이터(객체 혹은 인스턴스)가 Serializable이 구현 되어 있어야 합니다.

즉, 캐싱할 데이터는 Serializable을 상속받은 클래스여야 합니다.


왜냐하면, ehcache가 JVM의 힙 메모리가 아닌 곳(offheap 혹은 disk)에 캐시를 저장하기 위해서는,

JVM 메모리에 인스턴스화 되어있는 객체의 데이터를 외부에서 사용할 수 있게 하기 위해 Serialize(직렬화)가 필요하기 때문입니다.


📘 Spring Dev-Tools

Spring Boot에서는 코드 수정 시 브라우저에 변경된 값을 반영 하려면, 재 시작을 해야 하는데 이런 불편한 점을 해결할 수 있습니다.


크게 아래 5가지 기능을 제공합니다.

  • Property Defaults (속성 기본값)
  • Automatic Restart (자동 재시작)
  • Live Reload (실시간 리로드)
  • Global Settings (전역 설정)
  • Remote Applications (원격 어플리케이션)

Automatic Restart

IDE Settings

  • IntelliJ - Settings - Advanced Settings - Compiler 부분에 Allow auto-make to start even ... 부분 체크
  • IntelliJ - Settings - Build, Execution - Compiler - Builld Project automatically 체크

Yaml 파일

  • spring.devtools.restart.enabled # Automatic Restart 사용 여부
  • spring.devtools.restart.additional-exclude # Automatic Restart 내에서 제외할 파일 경로
spring:
    devtools:
        restart:
            enabled: true
            additional-exclude: static/**, public/**

Live Reload

IDE Settings

IntellJ - Edit Configuration - Modify Options - On Update Option - Resource Update 체크


Yaml 파일

spring:
    devtools:
        livereload:
            enabled: true