이번시간에는 Spring boot에서 지원해주는 캐시와 spring boot의 에러 페이지를 알아보자.

Spring boot 에서는 다양한 캐시들을 지원한다. 기본적으로 아무 설정 하지 않았을 경우에는 ConcurrentMapCacheManager의 ConcurrentHashMap을 사용해서 캐시를 사용하고 JSR-107 (JCache) 명세에 따른 캐시도 지원한다. 그 구현체들은 EhCache, Hazelcast, Infinispan, apache ignite 등이 있다.(이 외에도 더 있던거 같았다.) 이외에도 JSR-107 표준 명세로는 따르지 않았지만 Redis, Caffeine, Guava등이 존재하고 있다. Caffeine 캐시 같은 경우에는 JCache도 지원한다.

일단 캐시를 사용할 수 있게 디펜더시를 받자.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

기본적으로 spring-boot-starter-cache를 디펜더시 받으면 된다. 그리고 나서 @EnableCaching를 선언하면 캐시 설정은 끝났다.

//...
@EnableCaching
public class SpringBootCleanBlogApplication {
//...
}

그리고 나서 바로 사용해도 된다. 아래와 같이 캐시를 사용할 메서드 위에 @Cacheable 어노테이션을 사용해서 키를 지정해주면 된다.

//...
@Cacheable("blog.category")
public Page<Category> findAll(Pageable pageable) {
//...
}

위와 같이만 해도 캐시 설정은 다 되었다. 하지만 좀 더 캐시를 구체적으로 다룰 필요가 있다. 캐시의 expired 시간이라던지 모니터링이라던지 기타 등등 좀더 나은 캐시를 사용해 보자.

일단 JSR-107 (JCache) 구현체인 EhCache를 설정 해보자.

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

일단 필요한 라이브러리인 cache-api 와 ehcache를 받으면 된다. 그리고 다음과 같이 설정을 하자.

@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
  return cm -> cm.createCache("blog.category", initConfiguration(Duration.ONE_MINUTE));
}

private MutableConfiguration<Object, Object> initConfiguration(Duration duration) {
  return new MutableConfiguration<>()
    .setStatisticsEnabled(true)
    .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(duration));
}

blog.category라는 키에 1분동안 캐시를 한다고 지정해줬다. Duration 에는 ONE_DAY, ONE_HOUR, THIRTY_MINUTES, TEN_MINUTES 기타 등등 여러가지의 시간이 있기는 하지만 거기에 없는 시간이 있다면 만들면 된다.

public static final Duration TEN_SECONDS = new Duration(TimeUnit.SECONDS, 10);

설정 속성중에 statisticsEnabled 모니터링을 할 수 있는 속성이다. 해당 캐시의 통계를 확인 할 수 있다. jconsole로 확인 가능하다. 잘되나 확인하기 위해 아래와 같이 카테고리 리스트에 캐시를 설정했다. 다른 곳에 하고 싶다면 키 설정만 잘 해주면 된다.

@Transactional(readOnly = true)
@Cacheable("blog.category")
public Page<Category> findAll(Pageable pageable) {
  log.info("blog.category cache");
  return categoryRepository.findAll(pageable);
}

일분에 한번 blog.category cache 로그가 출력되면 성공적으로 캐시가 설정 되었다. 1분에 한번은 너무 기니 10초 정도만 설정 후 테스트 해보자.
JSR-107 캐시 중 에 다른 라이브러리를 사용하고 싶다면 설정 코드는 고치지 않고 해당하는 디펜디시만 바꾸면 된다.

<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-spring4-embedded</artifactId>
</dependency>
<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-jcache</artifactId>
</dependency>

infinispan 캐시를 사용하고 싶다면 위와 같이 설정 하면 된다.
아래는 hazelcast 디펜더시 방법이다.

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
</dependency>
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-spring</artifactId>
</dependency>

다음은 apache.ignite 캐시 디펜더시 이다. Spring 문서에는 apache.ignite 없지만 JSR-107 표준을 잘 따랐다면 문제 없이 동작 할 듯 하다. 테스트를 해본 결과 되긴 하나 뭔가 조금 찜찜한 구석이 몇가지 있던거 같았다. WARN 로그도 뜨고 그랬던거 같았는데 자세히 보지는 않았다. 그냥 JSR-107 구현체로 만들어 졌다고 하길래 테스트를 해본거 뿐이다. Spring 문서에도 없어 Spring을 사용할 때는 그닥 사용할 일이 없을 거 같다.

<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-core</artifactId>
    <version>1.7.0</version>
</dependency>

이번에는 간단하게 Caffeine 사용해 보자.
일단 카페인 캐시를 디펜더시 받자.

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

디펜더시를 받은 후에 application.properties에 다음과 같이 작성하자.

spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=60s

maximumSize은 캐시의 최대 사이즈고 expireAfterWrite은 expired시간이다. 분단위로 하고 싶다면 m으로 하면 된다. 시간은 h로 하면되나?
필자도 디테일하게 설정방법을 아직 모른다. 나중에 사용할 기회가 된다면 한번 알아봐야겠다. 문서도 잘 되어있는거 같으니 한번 살펴보는 것도 나쁘지 않다.
Guava 설정 방법도 거의 동일하다. 구아바 라이브러리를 디펜더시 받은후에 프로퍼티에 다음과 같이 해주면된다.

spring.cache.guava.spec=maximumSize=500,expireAfterAccess=600s

아까 위에서 말했듯이 카페인 캐시는 JCache 도 지원한다. 디펜더시만 살펴보자.

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>jcache</artifactId>
    <version>2.3.3</version>
</dependency>

위와 같이 jcache를 디펜더시 받으면 된다. spring boot에서는 버전관리를 해주지 않는다. 그러므로 버전정보를 입력해야된다.

캐시를 하고 싶은곳에 @Cacheable을 선언한 후에 Duration time만 원하는 시간으로 해준다면 쉽게 캐시를 설정 할 수 있다.
이렇게 카테고리쪽에 캐시를 달아봤다. 현재 카테고리쪽에는 어울리지 않을 수도 있지만 개발을 하다가 캐싱하고 싶은 부분이 있다면 위와 같이 하면 될 것이다.

이번에는 블로그의 에러 페이지를 만들어보자. Spring boot 1.4 부터는 너무나도 간단하게 에러페이지를 만들 수 있다. 뷰 템플릿을 사용한다면 templates 폴더 밑에 error라는 폴더를 만들고 뷰 템플릿을 사용하지 않는다면 static아래 error폴더를 만들면 된다. error폴더 밑에 해당하는 에러코드로 파일명을 만들면 된다. 예를들어 404 에러이면 404.html을 만들면 되고 500일 경우에는 500.html을 만들면 된다.
또한 400대 에러를 통 틀어서 4xx.html로 파일명을 만들어도 된다.

필자는 4xx.html을 만들었다. 모든 에러 코드를 넣기엔 양이 많으니 한개로 퉁 쳤다.

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layouts/main">
<head>
    <meta charset="UTF-8"/>
    <title th:text="'Ooops, ' + ${error}">Ooops, page not found</title>
    <style type="text/css">
        .wrapper {
            width: 100%;
            text-align: center;
        }
        .big {
            font-size: 100%
        }
    </style>
</head>
<body>
<div class="wrapper" layout:fragment="content">
    <div class="big">
        <h1>¯\_(ツ)_/¯</h1>
        <br />
        <h2 th:text="${error}"></h2>
    </div>
</div>
</body>
</html>

한번 404에러를 내보자. 없는 url을 만들어서 입력해보자.
7.blog1

이번에는 지원하지 않는 요청 메서드에러이다.
7.blog2

에러페이지도 나쁘지 않게 나왔다. 가만보니까 상단 배경이미지 위에 글씨가 없다. 이것 또한 에러가 나오게 만들어주자.
main.htm에 navSection을 넣어 준 곳에 가서 아래와 같이 살짝 변경해주자.

<h1 th:if="${navSection}" style="color:#ffffff" th:text="${navSection}">Clean Blog</h1>
<h1 th:unless ="${navSection}" style="color:#ffffff" th:text="${error}">Error</h1>

navSection 있다면 navSection를 출력해주고 그렇지 않다면 error를 출력해주면 된다. 다시 확인 해보자.

7.blog3

나쁘지 않게 나왔다. 한번 5xx 대 에러 페이지도 만들어 보자.
기존의 페이지와 똑같다. 텍스트 이모티콘만 변경했다. 5xx.html을 만들고 아래와 같이 넣자.

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layouts/main">
<head>
    <meta charset="UTF-8"/>
    <title th:text="'Sorry, ' + ${error}">Sorry, </title>
    <style type="text/css">
        .wrapper {
            width: 100%;
            text-align: center;
        }
        .big {
            font-size: 100%
        }
    </style>
</head>
<body>
<div class="wrapper" layout:fragment="content">
    <div class="big">
        <h1>(๑′°︿°๑)</h1>
        <br />
        <h2 th:text="'Sorry, ' + ${error}"></h2>
    </div>
</div>
</body>
</html>

7.blog4

위와 같이 500대 에러가 났을 경우에는 5xx.html을 보여 준다.

이렇게 캐시와 에러페이지를 만들어 봤다. 10편 안에 마무리 될듯 하다.
현재까지의 소스는 여기에 있다.

  1. [spring-boot] 블로그를 만들자 (1)

  2. [spring-boot] 블로그를 만들자. (2) JPA

  3. [spring-boot] 블로그를 만들자. (3) Category 와 Comment

  4. [spring-boot] 블로그를 만들자. (4) thymeleaf

  5. [spring-boot] 블로그를 만들자. (5) markdown, catetory

  6. [spring-boot] 블로그를 만들자. (6) 댓글과 Navigation

  7. [spring-boot] 블로그를 만들자. (7) 캐시와 에러페이지

  8. [spring-boot] 블로그를 만들자. (8) GitHub login

  9. [spring-boot] 블로그를 만들자. (9) CI 와 배포