Notice
Recent Posts
Recent Comments
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

Keep calm and code on

Spring MVC Custom Annotation 활용한 부가기능 추가하기 본문

Spring

Spring MVC Custom Annotation 활용한 부가기능 추가하기

Sejong Park 2016. 11. 19. 15:45

스프링 MVC를 사용한다고 한다면 모든 웹 요청은 기본적으로 DispatcherServlet을 통해서 들어오게 된다.이 DispatcherServlet은 자바의 표준 Servlet을 확장(혹은 상속. extends키워드는 확장이 더 옳은 표현이라고 본다.)한 클래스로 핸들러 매핑 메서드와의 연결과 에러처리, 뷰 렌더링 등 수많은 작업들이 일어나는 곳이다.




아주아주 역사가 깊은(또는 복잡한…​) 자바 표준 Servlet을 잘 감싸준 이DispatcherServlet 덕분에 우리는 아래와 같이 간결하게 URL매핑 코드를 만들어줄 수 있다.


@Controller
public class MainController {
 
    @GetMapping("/main")
    public String getMainView(){
        //doSomething 
        return "/index";
    }
}


컨트롤러에서는 @RequestMapping이라는 어노테이션을 이용해 우리는 URL이나 method등에 따라 처리하는 핸들러메서드를 구현할 수 있다. 컨트롤러를 보면 알겠지만 어노테이션은 가독성이 매우 뛰어나다. 어노테이션을 활용한 코드는 한눈에 해당 메서드가 어떠한 역할을 하는지를 쉽사리 파악할 수 있도록 만들어주며, 코드에 간접적으로 영향을 끼치게 되므로 추가가 비교적 자유롭다.

여기서 한발만 더 나아가 보도록 하자. 권한을 체크하여야 한다던지 로깅을 한다던지와 같은 다양한 부가작업들을 커스텀 어노테이션을 통해서 표기할 수 있다면 가독성있는 코드와 간결함이라는 두마리 토끼를 동시에 잡아낼 수 있을것이다. 특정 페이지에 접속할때마다 시간을 로그에 출력해야 한다는 요구사항이 있는 경우, @AccessLogging이라는 커스텀 어노테이션을 통해서 이를 구현해보도록 하자.

이를 코드로 정리하면 아래와 같다.


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLogging {
}
 
@RestController
public class SimpleController {
 
    @AccessLogging
    @GetMapping("/test1")
    public String simpleHandler1(){
        return "Called test1";
    }
 
    //... 
}




DispatcherServlet에서 요청은 위와 같은 순서로 처리되게 된다. 여기서HandlerInterceptor는 서블릿의 필터와 유사하게 요청과 응답에 부가적인 로직을 삽입해 줄 수 있도록 해준다. 스프링에서 요청을 할때나 응답을 받을때마다 HandlerInterceptor가 정의된 순서대로 처리하여 부가적인 작업을 처리하도록 해준다.


HandlerInterceptor는 총 3개의 메서드를 가지고 있다. 첫번째 preHandle는 요청이 들어올 경우 수행되며, 두번째 postHandle은 응답을 할 때 부가적인 로직을, 세번째afterCompletion는 요청이 전부 종료되었을때 수행되는 메서드이다.

이제 이를 활용하여 요청에 특정 어노테이션이 있는지 체크하고, 있을경우 로깅을 하는 로직을 구현해 보도록 하자. 요청이 들어올 때 마다 어떠한 값을 찍는게 목표이므로,preHandle이 해당 로직을 구현해야 할 지점으로 볼 수 있다.

class AccessLoggingInterceptor implements HandlerInterceptor {
 
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object rawHandler) throws Exception {
       HandlerMethod handler = HandlerMethod.class.cast(rawHandler);
       if (handler.hasMethodAnnotation(AccessLogging.class)) {
           String handlerName = handler.getMethod().getName();
           String time = LocalDateTime.now().toString();
 
           System.out.println(String.format("methodName : %s accessTime : %s", handlerName, time));
       }
 
       return true;
   }
 
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       // doNothing 
   }
 
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       // doNothing 
   }
}


위의 구현체에서 preHandle메서드의 3번째 파라미터로 입력되는 핸들러를 가져올 수 있다. 우리는 지금 HandlerMethod만을 체크할 것이므로 Object가 해당 타입으로 변환가능한지 확인하여야 한다. 그 다음 타입이 맞다면 변환을 수행한다.

HandlerMethod 객체에서 해당 핸들러의 다양한 속성들을 가져올 수 있다. 어노테이션의 선언여부 또한 알 수 있다. AccessLogging 어노테이션이 있는지 확인한 다음 만약 값이 존재한다면, 로그를 출력하도록 처리하도록 처리할 수 있다.

마지막으로 작성된 HandlerInterceptor를 추가해 주어야 한다. Spring Boot에서는 다음과 같이 설정이 가능하다.

@Configuration
public class InterceptorConfiguration extends WebMvcConfigurerAdapter {
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AccessLoggingInterceptor());
    }
}

WebMvcConfigurerAdapter는 웹과 관련된 설정을 재정의할 수 있는 확장포인트이다. 그리고 해당 설정 컨피그의 addInterceptors를 이용하여 우리가 정의한 interceptor를 추가할 수 있다. 그리고 서버를 실행시킨 다음 해당 URL을 호출하면 console에 요구사항이였던 시간과 메서드명이 찍히는 모습을 확인할 수 있을 것이다.


2016-11-19 14:53:14.905  INFO 39698 --- [           main] net.chandol.study.Application            : Started Application in 6.38 seconds (JVM running for 7.282)
2016-11-19 14:53:24.353  INFO 39698 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-11-19 14:53:24.354  INFO 39698 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2016-11-19 14:53:24.382  INFO 39698 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 28 ms
methodName : simpleHandler1 accessTime : 2016-11-19T14:53:24.436
methodName : simpleHandler1 accessTime : 2016-11-19T14:53:25.293
methodName : simpleHandler1 accessTime : 2016-11-19T14:53:25.553


지금까지 구현한 코드는 아래의 깃헙에서 확인할 수 있다.


'Spring' 카테고리의 다른 글

Spring Scheduling 작동 주기 동적으로 설정하기  (0) 2016.11.16
Comments