이번 글에서는 Spring InterCeptor에 대해 Demo를 제작하면서 알아보겠습니다.

모든 소스는 Github에서 확인가능합니다.

Demo의 내용은 Interceptor를 통해 Request를 가로채서 URI 패턴에 따른 응답 처리를 해보겠습니다.

소스 설명에 들어가기에 앞서 간단하게 Spring MVC Request life cycle에 대해 이미지로 확인을 해보면

사용자(Clinet)가 요청이 어떤 단계를 거치게 되는지 알 수가 있습니다.

 

 

여기서 눈여겨볼 부분은 HandlerInterceptor 부분인데 이 부분에 대해 실제 구현을 한다고 보면 됩니다.

구조를 보고 Filter 영역에서 처리하면 되는 것이 아닌지에 대해 의문을 갖는 분이 계실 것 같은데

차이점을 설명하자면 실행되는 시점이 다르기때문에 예외 처리에 대한 부분이 다릅니다.

Filter 같은 경우 예외가 발생하면 예외 처리 페이지로 보낸다던지 직접 처리가 어려운 반면

interceptor의 경우에는 예외에 대해 세부적으로 직접 핸들링이 가능합니다.

 

※ 개발환경 :  JDK 11, STS 4.8.1,  Spring Boot 2.6.3, Maven 4

 

1. Demo 프로젝트 생성

프로젝트, 패키지 명은 적절하게 선택하여 Spring Stater Project 생성합니다.

의존성 설정은 아래의 pom.xml 파일을 참고해주세요.

 

2. Demo 프로젝트 구조

아래 이미지와 같은 구조를 가지게 됩니다.

3.  pom.xml 파일 설정

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>SpringbootHttpInterceptor</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringbootHttpInterceptor</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

4. HttpInterceptor 설정 및 추가

4.1  HttpInterceptor 설정 

src/main/java > com.example.demo.config 패키지를 생성하고 HttpInterceptorConfig 클래스를 작성합니다.

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class HttpInterceptorConfig implements WebMvcConfigurer {

	@Autowired
	@Qualifier("httpInterceptor")
	private HandlerInterceptor handlerInterceptor;
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(handlerInterceptor)
		.addPathPatterns("/demo/user/**")
		.addPathPatterns("/demo/visitor")
		.excludePathPatterns("/demo/product/**");
		
	}
	
}

 

  • addInterceptors 메서드
    사용할 handlerInterceptor를 추가하고, addPathPatterns을 사용하여 가로챌 URI 패턴에 대해 정의한다.
    excludePathPatrrerns를 사용해서 가로채지 않을 URI 패턴에 대해 정의한다.

 

4.2 HttpInterceptor 

src/main/java > com.example.demo.common 패키지를 생성하고

HandlerIntercpetor를 구현하는 클래스를 작성합니다.

package com.example.demo.common;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class HttpInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		
		System.out.println("[preHandle]");
		
		try {
		 	String reqUri= request.getRequestURI();
		 	if (reqUri.startsWith("/demo/user/")) {
		 		throw new Exception("사용이 중지된 API 입니다.");
		 	}
		 	
		} catch(Exception e) {
		 	String result = getResponse(e.getMessage(), HttpStatus.OK);
			response.setCharacterEncoding("UTF-8");
			response.setContentType("application/json;charset=utf-8");
			response.getWriter().write(result);
			return false;
		} 
		return true;
	}
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
		System.out.println("[postHandle]");
		
		HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("[afterCompletion]");
		HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
	}
	
	
	private String getResponse(String msg, HttpStatus httpStatus) throws Exception {
	 	ResponseEntity<?> responseEntity = new ResponseEntity<>(msg, httpStatus);
	 	ObjectMapper objectMapper = new ObjectMapper();
		return objectMapper.writeValueAsString(responseEntity);
	}
	
}
  • preHandle 메서드
    Handler를 실행하기 전 실행되는 메서드.
    특정 URI 패턴을 확인하여 컨트롤러 상에 작성된 Request URI와 맵핑되기 전에
    요청을 가로채서 처리합니다.
  • postHandle 메서드
    Handler 실행 후 실행되는 메서드.
  • afterComletion 메서드
    View를 렌더링한 후에 실행되는 메서드.

 

5. DemoController 

src/main/java > com.example.demo.controller 패키지를 생성하고 DemoController 클래스를 작성합니다.

package com.example.demo.controller;

import java.util.HashMap;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {
	
	   @GetMapping("/demo/user/{id}")
	   public ResponseEntity<?> getUserInfoById(@PathVariable("id") String id){
		   HashMap<String, String> map = new HashMap<>();
		   map.put("id", id);
		   map.put("name", "jack");

	       return new ResponseEntity<>(map, HttpStatus.OK);
	   }
	   
	   @GetMapping("/demo/user/{id}/phone")
	   public ResponseEntity<?> getUserPhoneInfo(@PathVariable("id") String id){
		   HashMap<String, String> map = new HashMap<>();
		   map.put("id", id);
		   map.put("phone", "010-1234-1234");

	       return new ResponseEntity<>(map, HttpStatus.OK);
	   }
	   
	   @GetMapping("/demo/product/{id}")
	   public ResponseEntity<?> getUserProductInfo(@PathVariable("id") String id){
		   HashMap<String, String> map = new HashMap<>();
		   map.put("id", id);
		   map.put("name", "ABC");
		   map.put("price", "1000");

	       return new ResponseEntity<>(map, HttpStatus.OK);
	   }
	   
	   @GetMapping("/demo/visitor")
	   public ResponseEntity<?> getSitemap(){
		   HashMap<String, String> map = new HashMap<>();
		   map.put("vistor", "100");
		   
	       return new ResponseEntity<>(map, HttpStatus.OK);
	   }
}

데모 테스트를 위한 API로 서비스, DAO 레이어는 생략했습니다.

  • /demo/user/{id}
    유저 정보를 조회하여 반환하는 API. 
    Interceptor 설정을 통해 등록된 URI 패턴입니다.
  • /demo/user/{id}/phone
    유저 전화번호를 조회하여 반환하는 API. 
    Interceptor 설정을 통해 등록된 URI 패턴입니다.

  • /demo/product/{id}
    상품 정보를 조회하여 반환하는 API.
    Interceptor 설정을 통해 제외 등록된 URI 패턴입니다.

  • /demo/visitor
    방문자 수를 조회하여 반환하는 API.
    Interceptor 설정을 통해 등록된 URI 패턴입니다.

6. 테스트

Spring Boot Dashboard에서 start 합니다.

POST MAN 같은 API 툴을 사용하여 API를 호출합니다.

 

/demo/visitor 호출 결과입니다.

정상적으로 응답이 오고 Interceptor에 모든 메서드에 로그가 출력된 것을 확인 가능합니다.

post man 결과
콘솔 창

/demo/user/{id}, /demo/user/{id}/phone 호출 결과입니다.

/demo/user/** 으로 설정했기 때문에 /demo/user/ 으로 시작하는 패턴은 모두 가로채 집니다.

post man 결과
콘솔 창

/demo/product/{id} 의 호출 결과입니다.

콘솔 창에는 아무것도 출력되지 않습니다. 가로채기가 제외된 URI 패턴이기 때문입니다.

 

7. 마치며

간단한 데모를 통하여 Interceptor에 대한 설정과 사용법에 대해 알아봤습니다.

해당 모듈은 공통 처리가 필요한 경우 아주 적절하다고 봅니다.

예를 들어 일부를 제외한 모든 API에 인증 토큰을 검사해야 한다고 한다면 공통 처리하지 않는 경우

해당되는 API에 어떤 식으로든 토큰 검사하는 모듈이 작성되어야 합니다.

만약 수백 개에 이른다고 하면 인터셉터에서 URI 패턴과 preHandler 메서드를 사용하여

비교적 쉽고 빠른 시간 안에 처리 가능하며, 추후에 유지보수도 용이합니다.

 

※ 참고

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-handlermapping-interceptor
https://meetup.toast.com/posts/151

'Spring Boot Framework' 카테고리의 다른 글

Spring RestTemplate (1)  (0) 2022.05.23
Spring Rest Docs 와 Swagger (2)  (0) 2022.02.12
Spring Rest Docs 와 Swagger (1)  (0) 2022.02.11
Spring Boot Oauth 2.0 과 JWT Demo (5)  (0) 2022.02.08
Spring Boot Oauth 2.0 과 JWT Demo (4)  (0) 2022.02.06

+ Recent posts