> Hello World !!!

     

@syaku

Spring Ehcache : 스프링 캐시 설정

Spring Ehcache Multiplex Configuration : 스프링 캐시 설정, 여러 캐쉬 설정 및 생성

스프링프레임워크 캐시에 EhCache 를 연동하는 방법과 캐시 생성에 대해 설명한다. 그리고 설정파일(ehcache.xml)은 하나로 사용하기 때문에 모듈단위로 구현되 애플리케이션은 캐시 설정을 개별적으로 관리할 수 없다. 그래서 설정을 모듈 개별적으로 관리할 수 있게 설정파일을 모듈 별로 가지고 모듈 단위로 캐시를 생성할 수 있도록 하는 방법을 소개한다. 좀 더 동적으로 EhCache의 캐시를 생성할 수 있도록 하기 위한 방법이다.

Ehcache는 테라코타에서 개발한 데이터 캐시 프로그램이다. 자바로 개발되었고 스프링에서 지원하는 cache를 이용하여 연동할 수 있다. 데이터를 메모리와 디스크에 기록할 수 있으며, 사용방법은 어렵지 않아 쉽게 사용할 수 있다. 

주로 반복적인 데이터를 로드할 때 캐시를 이용하여 로드 속도를 개선하여 안정적인 서비스를 지원할 수 있게 한다. DBMS에서 가져온 데이터를 캐싱해두면 다시 DBMS에 들리지 않고 메모리에서 가져오기 때문에 빠르게 처리한다.

최근에 3버전까지 릴리즈되었으나 아직 스프링프레임워크에서 지원하지 않아 2.10.x 버전을 사용한다. 하지만 3.x 버전도 스프링프레임워크에서 사용할 수있다. 방법은 구글링을 통해 얻을 수 있다.

Github 소스

https://github.com/syakuis/spring-ehcache

개발환경

Java 1.7
Spring 4.2.4.RELEASE
Ehcache 2.10.2

Ehcache 설정 옵션

EhCache 의 설정 옵션에 대해 하나하나 설명하였다. 참고사이트 : http://www.ehcache.org/ehcache.xml

디스크(DiskStore) 저장 경로 설정

케시를 디스크에 저장할 경우 경로를 임의적으로 변경하여 사용할 수 있다.

<diskStore path="java.io.tmpdir"/>

java.io.tmpdir는 임시저장경로를 사용할때 설정한다.

VM Option에서 경로를 지정하려면 아래와 같이 사용한다.

java -Dehcache.disk.store.dir=경로

세부적인 설정 옵션

name: 캐시 이름을 설정한다. 캐시를 식별할때 사용한다.

maxEntriesLocalHeap: 메모리에 생성될 객체의 최대 수를 설정한다. 0=제한없음

maxEntriesLocalDisk: 디스크(DiskStore)에 저장될 객체의 최대 수를 설정한다. 0=제한없음

eternal: 저장된 캐시를 제거할지 여부를 설정한다. true 인 경우 저장된 캐시는 제거되지 않으며 timeToIdleSeconds, timeToLiveSeconds 설정은 무시된다.

timeToIdleSeconds: 생성후 해당 시간 동안 캐쉬가 사용되지 않으면 삭제된다. 0은 삭제되지 않는 다. 단 eternal=false 인 경우에만 유효하다.

timeToLiveSeconds: 생성후 해당 시간이 지나면 캐쉬는 삭제된다. 0은 삭제되지 않는 다. 단 eternal=false 인 경우에만 유효하다.

diskExpiryThreadIntervalSeconds: 디스크(DiskStore)에 저장된 캐시들을 정리하기 위한 작업의 실행 간격 시간을 설정한다. 기본값은 120초

diskSpoolBufferSizeMB: 스풀버퍼에 대한 디스크(DiskStore) 크기 설정한다. OutOfMemory 에러가 발생 시 설정한 크기를 낮추는 것이 좋다.

clearOnFlush: flush() 메서드가 호출되면 메모리(MemoryStore)가 삭제할지 여부를 설정한다. 기본값은 true 이며, 메모리(MemoryStore)는 삭제된다.

memoryStoreEvictionPolicy : maxEntriesLocalHeap 설정 값에 도달했을때 설정된 정책에 따리 객체가 제거되고 새로 추가된다.

LRU: 사용이 가장 적었던 것부터 제거한다.
FIFO: 먼저 입력된 것부터 제거한다.
LFU: 사용량이 적은 것부터 제거한다. 

logging: 로깅 사용 여부를 설정한다.

maxEntriesInCache: Terracotta의 분산캐시에만 사용가능하며, 클러스터에 저장 할 수 있는 최대 엔트리 수를 설정한다. 0은 제한이 없다. 캐시가 작동하는 동안에 속성을 수정할 수 있다.

overflowToOffHeap: 이 설정은 Ehcache 엔터프라이즈 버전에서 사용할 수 있다. true 로 설정하며 성능을 향상시킬 수 있는 Off-heap 메모리 스토리지를 활용하여 캐시를 사용할 수 있다. Off-heap 메모리 자바의 GC에 영향을 주지않는 다. 기본값은 false

overflowToDisk: Deprecated
diskPersistent: Deprecated
diskAccessStripes: Deprecated
maxElementsInMemory: Deprecated
maxElementsOnDisk: Deprecated
maxMemoryOffHeap: Deprecated 

Ehcahce 사용한 웹어플리케이션 구현

빌드 설정 및 의존성 설정

Gradle 빌드툴을 사용했다. Maven 을 사용할 경우 아래의 의존성(dependencies)을 추가해주면 된다.

buildscript {
    repositories {
        maven { url 'http://repo.spring.io/plugins-release' }
    }
    dependencies {
        classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
    }
}

group 'org.syaku'
version '1.0-SNAPSHOT'

apply plugin: 'propdeps'
apply plugin: 'propdeps-idea'

apply plugin: 'java'
apply plugin: 'idea'

sourceCompatibility = 1.7
targetCompatibility = 1.7

ext { }

ext.slf4jVersion = '1.7.12'
ext.junitVersion = '4.9'
ext.springVersion = '4.2.4.RELEASE'

ext.ehcacheVersion = '2.10.2'

configurations.all {
    exclude group: "commons-logging", module: "commons-logging"
}

repositories {
    mavenCentral()
}

dependencies {
    compile "org.slf4j:jcl-over-slf4j:${slf4jVersion}"
    runtime("ch.qos.logback:logback-classic:1.1.7") {
        exclude group: 'org.slf4j', module: 'slf4j-api'
    }

    testCompile "junit:junit:${junitVersion}"

    testCompile "org.springframework:spring-test:${springVersion}"

    compile "org.springframework:spring-context:${springVersion}",
            "org.springframework:spring-context-support:${springVersion}"

    compile "net.sf.ehcache:ehcache:${ehcacheVersion}"
}

스프링 spring-context-support 의존성에서 ehcache 연동을 위한 라이브러리를 제공하고 있다.

Ehcache 설정 하기

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true"
         monitoring="autodetect"
         dynamicConfig="true">

    <diskStore path="java.io.tmpdir" />

    <cache name="test"
        maxEntriesLocalHeap="10000"
        timeToIdleSeconds="10"
        timeToLiveSeconds="20"
        memoryStoreEvictionPolicy="LFU">
        <persistence strategy="localTempSwap" />
    </cache>

</ehcache>

test 캐시를 설정하고 생성하기 위한 설정이다.

Spring Ehcache 연동 설정

Java 기반 설정

@Configuration
@ComponentScan(
        basePackages = "org.syaku.service",
        includeFilters =  {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)
        }
)
@EnableCaching
public class XmlCacheContext {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @PostConstruct
    public void prepare() {
        logger.debug("EhCache Xml Config");
    }

    @Bean
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
        cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cmfb.setShared(true);
        return cmfb;
    }
}

xml을 이용한 네임스페이스 기반 설정

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehcache"/>
</bean>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:config/ehcache.xml"/>
    <property name="shared" value="true"/>
</bean>

프로퍼티 설명

shared = cacheManager 싱글톤 여부. cacheManager 인스턴스가 없으면 생성한다. acceptExisting = cacheManager 이름 중복 사용여부. 동일한 이름이 있는 경우 생성하지 않고 해당 관리자를 리턴한다. 위 두개 프로퍼티가 false 인 경우 CacheManager 생성한다. 

프로퍼티는 위에서부터 아래로 순차적으로 처리되면 3개중에 한개만 실행된다.

Ehcache 테스트 하기

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { XmlCacheContext.class })
public class EhcacheXmlTest {

    @Autowired private CacheManager cacheManager;

    @Test
    public void live() throws InterruptedException {
        Cache cache = cacheManager.getCache("sample");

        // 캐시에 title 엘리먼트 추가
        cache.put("title", "ehcache testing...");

        // 캐시에 title 엘리먼트 호출
        System.out.println(cache.get("title").get());

        // 5초후 캐시에 title 엘리먼트 호출
        Thread.sleep(5000);
        System.out.println(cache.get("title").get());

        // 10초후 캐시에 title 엘리먼트 호출 하지만 10초동안 캐시를 호출하지 않으면 캐시가 삭제되게 됨 결과는 null
        Thread.sleep(10000);
        System.out.println(cache.get("title"));
    }
}

결과 로그

2016-07-27 16:51:45 DEBUG net.sf.ehcache.Cache - Initialised cache: sample
2016-07-27 16:51:45 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'sample'.
2016-07-27 16:51:45 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'sample'.
2016-07-27 16:51:45 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
ehcache testing...
2016-07-27 16:51:45 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-27 16:51:45 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
ehcache testing...
2016-07-27 16:52:00 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from heap
2016-07-27 16:52:00 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from disk
null

어노테이션을 이용한 캐시 사용하기

스프링 캐시 메뉴얼: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html

선언적인 어노테이션 기반의 캐싱 설정을 하기 위해 어노테이션 캐시를 활성화해야 한다.

# 자바설정
@EnableCaching

# xml설정
<cache:annotation-driven />

@Cacheable: 데이터를 캐시에 최초 저장한다. 저장된 캐시가 있으면 저장하지 않고 저장된 데이터를 사용한다.
@CacheEvict: 캐시의 데이터를 삭제한다. 메서드가 호출될때 마다 캐시를 삭제할때 사용된다.
@CachePut: 캐시의 데이터를 갱신다. 메서드가 호출될때 마다 캐시가 갱신되어야할때 사용된다.
@Caching: 여러가지 작업을 해야할 경우 사용한다. 즉 여러 어노에티션을 설정할 수 있다.

@Cacheable("캐시명") 혹은 @Cacheable(value = "캐시명") 혹은 @Cacheable(cacheNames = "캐시명") 사용한다.

// 소스경로 : src/main/java/org/syaku/service/EhcacheAnnotationService.java
@Service
public class EhcacheAnnotationService {

    @Cacheable("test")
    public String getDate() {
        return new Date().toString();
    }

    @CacheEvict("test")
    public String evict() {
        return new Date().toString();
    }

    @CachePut("test")
    public String put() {
        return new Date().toString();
    }
}

... end ...

// 소스경로 : src/test/java/org/syaku/EhcacheXmlTest.java
@Test
public void annotation() throws InterruptedException {
    // 현재 시간을 캐시에 저장한다.
    System.out.println("#1 ==========>" + ehcacheService.getDate());

    Thread.sleep(5000);
    // 저장된 캐시를 삭제하고 현재 시간을 캐시에 저장한다.
    System.out.println("#2 ==========>" + ehcacheService.evict());

    Thread.sleep(5000);
    // 저장되어 있는 캐시를 얻는 다. (이미 저장되어 있는 데이터가 있기 때문에)
    System.out.println("#3 ==========>" + ehcacheService.getDate());

    Thread.sleep(5000);
    // 저장되어 있는 캐시를 현재 시간으로 갱신한다.
    System.out.println("#4 ==========>" + ehcacheService.put());

    Thread.sleep(5000);
    // 저장되어 있는 캐시를 얻는 다. (이미 저장되어 있는 데이터가 있기 때문에)
    System.out.println("#5 ==========>" + ehcacheService.getDate());

    Thread.sleep(10000);
    // 캐시 갱신기간이 만료되어 삭제되었다. 그리고 현재 시간을 캐시에 저장한다.
    System.out.println("#6 ==========>" + ehcacheService.getDate());
}

테스트 결과 로그

2016-07-28 11:05:04 DEBUG net.sf.ehcache.Cache - Initialised cache: test
2016-07-28 11:05:04 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'test'.
2016-07-28 11:05:04 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'test'.
2016-07-28 11:05:04 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
#1 ==========>Thu Jul 28 11:05:04 KST 2016
2016-07-28 11:05:04 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 11:05:04 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
2016-07-28 11:05:09 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from heap
2016-07-28 11:05:09 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from disk
#2 ==========>Thu Jul 28 11:05:09 KST 2016
2016-07-28 11:05:14 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
#3 ==========>Thu Jul 28 11:05:14 KST 2016
2016-07-28 11:05:14 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 11:05:14 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
2016-07-28 11:05:19 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
2016-07-28 11:05:19 DEBUG net.sf.ehcache.store.disk.Segment - put updated, deleted 0 on heap
2016-07-28 11:05:19 DEBUG net.sf.ehcache.store.disk.Segment - put updated, deleted 0 on disk
#4 ==========>Thu Jul 28 11:05:19 KST 2016
2016-07-28 11:05:19 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 11:05:19 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
#5 ==========>Thu Jul 28 11:05:19 KST 2016
2016-07-28 11:05:34 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from heap
2016-07-28 11:05:34 DEBUG net.sf.ehcache.store.disk.Segment - remove deleted 0 from disk
2016-07-28 11:05:34 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
#6 ==========>Thu Jul 28 11:05:34 KST 2016
2016-07-28 11:05:34 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 11:05:34 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk

어노테이션을 이용한 캐시 사용할때 키와 키생성기를 사용하기

하나의 캐시에 키로 구분하여 여러가지 데이터를 보관할 수 있다. 주의할점은 메서드에 어노테이션 캐시를 설정할때 key를 사용할 경우에는 해당 캐시에 모두 key를 넣어주어야 한다. 그렇지 않고 key가 없는 메서드에서 캐싱할 경우 모든 캐시가 사라지거나 갱신된다.

@Cacheable(cacheNames = "test")
public String getDateKey() {
    return new Date().toString();
}

@Cacheable(cacheNames = "test", key = "#root.methodName")
public String getDateKey2() {
    return new Date().toString();
}

위 2개 캐시가 있다면 키가 없는 getDate 메서드가 호출될 경우 getDateKey 에서 저장된 캐시도 사라지게 된다는 것이다. 그래서 아래와 같이 한다면 두 캐시모두 유효하게 된다.

@Cacheable(cacheNames = "test", key = "#root.methodName")
public String getDateKey() {
    return new Date().toString();
}

@Cacheable(cacheNames = "test", key = "#root.methodName")
public String getDateKey2() {
    return new Date().toString();
}

... end ...

@Test
public void annotationKey() throws InterruptedException {
    // 현재시간을 캐시에 키 이름을 getDateKey로 하여 저장한다.
    System.out.println("#1 ==========>" + ehcacheService.getDateKey());
    Thread.sleep(5000);

    // 현재시간을 캐시에 키 이름을 getDateKey2로 하여 저장한다.
    System.out.println("#2 ==========>" + ehcacheService.getDateKey2());
    Thread.sleep(5000);

    // 캐시에 저장된 키 값이 getDateKey인 데이터를 출력한다. (이미 저장되어 있는 캐시를 가져온다.)
    System.out.println("#1 ==========>" + ehcacheService.getDateKey());
    Thread.sleep(5000);

    // 캐시에 저장된 키 값이 getDateKey2인 데이터를 출력한다. (이미 저장되어 있는 캐시를 가져온다.)
    System.out.println("#2 ==========>" + ehcacheService.getDateKey2());
}

테스트 결과 로그

2016-07-28 15:03:18 DEBUG net.sf.ehcache.Cache - Initialised cache: test
2016-07-28 15:03:18 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'test'.
2016-07-28 15:03:18 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'test'.
2016-07-28 15:03:18 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
#1 ==========>Thu Jul 28 15:03:18 KST 2016
2016-07-28 15:03:18 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 15:03:18 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
2016-07-28 15:03:23 DEBUG net.sf.ehcache.store.disk.Segment - put added 0 on heap
#2 ==========>Thu Jul 28 15:03:23 KST 2016
2016-07-28 15:03:23 DEBUG net.sf.ehcache.store.disk.Segment - fault removed 0 from heap
2016-07-28 15:03:23 DEBUG net.sf.ehcache.store.disk.Segment - fault added 0 on disk
#1 ==========>Thu Jul 28 15:03:18 KST 2016
#2 ==========>Thu Jul 28 15:03:23 KST 2016

동적으로 캐시를 생성하기

동적 캐시 생성은 프로퍼티에 설정된 정보를 이용하여 스프링 빈 팩토리를 통해 캐시 빈을 생성한다.

public class EhcacheFactoryBean implements FactoryBean<CacheManager>, InitializingBean, DisposableBean {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private String[] configLocations;

    private String cacheManagerName = "cacheManager";

    private boolean acceptExisting = false;

    private boolean shared = false;

    private CacheManager cacheManager;

    private boolean locallyManaged = true;


    public void setConfigLocation(String configLocation) {
        if (configLocation != null) {
            this.configLocations = configLocation.split(",");
        }
    }

    public void setConfigLocations(String[] configLocations) {
        this.configLocations = configLocations;
    }

    public void setCacheManagerName(String cacheManagerName) {
        this.cacheManagerName = cacheManagerName;
    }

    public void setAcceptExisting(boolean acceptExisting) {
        this.acceptExisting = acceptExisting;
    }

    public void setShared(boolean shared) {
        this.shared = shared;
    }


    @Override
    public void afterPropertiesSet() throws CacheException {
        logger.info("Initializing EhCache CacheManager");
        Configuration configuration;

        if (configLocations == null) {
            configuration = ConfigurationFactory.parseConfiguration();

        } else {
            configuration = new Configuration();

            configuration.setDynamicConfig(true);
            configuration.setUpdateCheck(true);
            configuration.setMonitoring("autodetect");

            try {
                PathMatchingResourceResolver resolver = new PathMatchingResourceResolver();
                Resource[] resources = resolver.getResources(configLocations);

                for(Resource resource : resources) {
                    Properties properties = new Properties();
                    properties.load(resource.getInputStream());

                    CacheConfiguration cacheConfiguration = new CacheConfiguration();

                    String cacheName = properties.getProperty("cacheName");
                    String memoryStoreEvictionPolicy = properties.getProperty("memoryStoreEvictionPolicy", "LRU");
                    int maxEntriesLocalHeap = Integer.parseInt(properties.getProperty("maxEntriesLocalHeap", "0"));
                    boolean eternal = properties.getProperty("eternal", "false").equals("true") ? true : false;
                    int timeToIdleSeconds = Integer.parseInt(properties.getProperty("timeToIdleSeconds", "0"));
                    int timeToLiveSeconds = Integer.parseInt(properties.getProperty("timeToLiveSeconds", "0"));
                    boolean loggin = properties.getProperty("loggin", "false").equals("true") ? true : false;

                    cacheConfiguration.setName(cacheName);
                    cacheConfiguration.setMemoryStoreEvictionPolicy(memoryStoreEvictionPolicy);
                    cacheConfiguration.setMaxEntriesLocalHeap(maxEntriesLocalHeap);
                    cacheConfiguration.setEternal(eternal);
                    cacheConfiguration.setTimeToIdleSeconds(timeToIdleSeconds);
                    cacheConfiguration.setTimeToLiveSeconds(timeToLiveSeconds);
                    cacheConfiguration.setLogging(loggin);
                    cacheConfiguration.persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP));

                    configuration.addCache(cacheConfiguration);
                }
            } catch (IOException e) {
                throw new CacheException(e.getMessage(), e);
            }
        }

        if (this.cacheManagerName != null) {
            configuration.setName(this.cacheManagerName);
        }

        if (this.shared) {
            // Old-school EhCache singleton sharing...
            // No way to find out whether we actually created a new CacheManager
            // or just received an existing singleton reference.
            this.cacheManager = CacheManager.create(configuration);
        }
        else if (this.acceptExisting) {
            // EhCache 2.5+: Reusing an existing CacheManager of the same name.
            // Basically the same code as in CacheManager.getInstance(String),
            // just storing whether we're dealing with an existing instance.
            synchronized (CacheManager.class) {
                this.cacheManager = CacheManager.getCacheManager(this.cacheManagerName);
                if (this.cacheManager == null) {
                    this.cacheManager = new CacheManager(configuration);
                }
                else {
                    this.locallyManaged = false;
                }
            }
        }
        else {
            // Throwing an exception if a CacheManager of the same name exists already...
            this.cacheManager = new CacheManager(configuration);
        }
    }


    @Override
    public CacheManager getObject() {
        return this.cacheManager;
    }

    @Override
    public Class<? extends CacheManager> getObjectType() {
        return (this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class);
    }

    @Override
    public boolean isSingleton() {
        return true;
    }


    @Override
    public void destroy() {
        if (this.locallyManaged) {
            logger.info("Shutting down EhCache CacheManager");
            this.cacheManager.shutdown();
        }
    }

}

테스트 구현

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ConfigContext.class, CacheContext.class })
public class EhcacheTest {

    @Autowired private CacheManager cacheManager;
    @Autowired private EhcacheAnnotationService ehcacheService;
    
    ...skip...
    
}

ConfigContext.class 프로퍼티 설정을 읽어오고, CacheContext.class 캐시 빈을 생성한다.

결과 로그

구동될때 출력되는 로그를 확인하면 sample, test 캐시가 생성되는 것을 확인이 된다.

2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file sample.data
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file sample.index
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Matching data file missing (or empty) for index file. Deleting index file /var/folders/p9/q2tymv4s18b6_z7l1nfjplzw0000gn/T/sample.index
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file sample.index
2016-07-28 17:15:10 DEBUG n.s.e.s.e.ExtendedStatisticsImpl - Mocking Pass-Through Statistic: LOCAL_OFFHEAP_SIZE

... skip ...

CLUSTER_EVENT
2016-07-28 17:15:10 DEBUG n.s.e.s.e.ExtendedStatisticsImpl - Mocking Operation Statistic: NONSTOP
2016-07-28 17:15:10 DEBUG net.sf.ehcache.Cache - Initialised cache: sample
2016-07-28 17:15:10 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'sample'.
2016-07-28 17:15:10 DEBUG n.s.e.config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'sample'.
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file test.data
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file test.index
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Matching data file missing (or empty) for index file. Deleting index file /var/folders/p9/q2tymv4s18b6_z7l1nfjplzw0000gn/T/test.index
2016-07-28 17:15:10 DEBUG n.s.e.store.disk.DiskStorageFactory - Failed to delete file test.index

주의: 선언적인 방식은 Spring aop 사용하기 때문에 Self Invocation(같은 클래스에서 메서드를 호출) 경우 캐싱이 되지 않는 다.



posted syaku blog

Syaku Blog by Seok Kyun. Choi. 최석균.

http://syaku.tistory.com