본문 바로가기
실습/리눅스 서버 + 스프링 부트

Filter, Interceptor, AOP

by 이민우 2022. 2. 1.
728x90
반응형

Dispatcher Servlet

 

필터, 인터셉터, aop를 알기 전에 먼저 디스패처 서블릿 (Dispatcher Servlet)을 알아야 할 것 같다.

디스패처 서블릿이란 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 수신하여 해당 프로토콜이 호출하는 컨트롤러에 위임해주는 프론트 컨트롤러이다.

사용자가 HTTP 기반 접근을 수행할 경우 가장 먼저 해당 프로토콜을 수신하는 역할인데, 필터와 인터셉터는 해당 디스패처 서블릿의 앞 뒤에서 작동한다.

그리고 AOP는 컨트롤러의 바로 앞단에서 수행된다.

 

전체적인 구조도는 아래의 링크에서 확인 가능하다.

그림으로 정리를 너무 잘해놓으셨다.

https://goddaehee.tistory.com/154

 

[Spring] Filter, Interceptor, AOP 차이 및 정리

[Spring] Filter, Interceptor, AOP 차이 및 정리 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ [Spring] 필터, 인터셉터, AOP 정리 ] 입니다. : ) 공통 프로세스에 대한 고민 자바 웹 개발을 하다보면, 공통..

goddaehee.tistory.com

 

Filter

 

필터는 디스패처 서블릿보다 앞단에서, 요청과 응답에 대한 정제를 담당한다.

즉 디스패처 서블릿보다 앞에서 실행되기에, 로그를 남기거나 request가 올바른 양식으로 전달되었는지 등에 대한 검사가 가능하다.

 

스프링에서는 javax.servlet.Filter 인터페이스를 직접 구현해야 한다. 형태는 아래와 같다.

package com.example.demo.filter;

import java.io.IOException;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@Order(1) //1
public class aopFilter implements Filter {

	public void init(FilterConfig filterConfig) throws ServletException {//2
	}
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {//3
		chain.doFilter(request, response);
		log.info((new Date()).toString() + " : " + request.getLocalAddr() + " => doFilter");
	}
	
	public void destroy() {//4
		
	}

}

Order : 만약 필터가 두 개 이상인 경우 순서를 지정한다.

init : 필터 객체를 초기화한다. 1회만 호출된다.

doFilter : 모든 HTTP 요청이 디스패처 서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드이다.

destroy : 필터 객체를 서비스에서 제거하는 메소드이다. init과 마찬가지로 1회만 호출된다.

 

Order은 순서를 정하는데, 그 수치는 낮을수록 우선순위가 높다.

그리고 org.springframework.core.annotation.Ordered.HIGHEST_PRECEDENCE 혹은 LOWEST_PRECEDENCE를 사용하면 우선순위를 각각 최상, 최하로 둘 수 있다.

 

참고로 여기서는 모든 http 메소드를 받기 위해 @Component 어노테이션을 사용하였으나, 만약 특정 url만 받고 싶다면 아래와 같은 어노테이션 사용도 가능하다.

@WebFilter(urlPatterns="/date")

단 이 경우 @SpringBootApplication 을 가진 클래스에 @ServletComponentScan 어노테이션도 추가되어야 한다.

 

 

 

Interceptor

 

필터를 거친 요청은 디스패처 서블릿을 지나 인터셉터에 의해 호출된다.

 

인터셉터는 org.springframework.web.servlet.HandlerInterceptor를 상속받아 구현하면 되고, 양식은 아래와 같다.

package com.example.demo.interceptor;

import java.util.Date;

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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.filter.aopFilter;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class AopInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception { //1
		log.info((new Date()).toString() + " : " + request.getLocalAddr() + " => preHandle");
		return HandlerInterceptor.super.preHandle(request, response, handler);
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception { //2
		log.info((new Date()).toString() + " : " + request.getLocalAddr() + " => postHandle");
		HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception { //3
		log.info((new Date()).toString() + " : " + request.getLocalAddr() + " => afterCompletion");
		HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
	}
	
}

preHandle : 컨트롤러 호출 전에 실행된다.

postHandle : 컨트롤러 호출 후에 실행된다.

afterCompletion : 모든 작업이 완료된 후 실행된다.

 

참고로 HandlerInterceptor 말고 HandlerInterceptorAdapter 를 상속받아 사용할 수도 있다.

차이점이라면 afterConcurrentHandlingStarted 메소드가 추가되었는데, 이는 비동기 요청 시 postHandle과 afterComplete 대신 실행된다.

 

그리고 인터셉터 적용을 위해 org.springframework.web.servlet.config.annotation.WebMvcConfigurer를 상속받은 설정 클래스를 만들어주어야 한다.

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.demo.interceptor.AopInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new AopInterceptor()) // 1
		.excludePathPatterns("/css/**", "/fonts/**", "/scripts/**"); // 2
	}

}

1. AopInterceptor 라는 방금 만든 인터셉터를 적용한다.

2. css, font 등의 공통된 요청에 대해서는 인터셉터를 적용하지 않는다.

 

 

 

 

AOP

 

AOP는 관점지향 프로그래밍 (Aspect Oriented Programming)의 약자로, 로깅, 파일 입출력 등의 공통 로직을 모듈화하여 사용하는 것이다.

 

AOP 사용에 앞서 필요 종속성을 pom.xml에 추가하자.

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

 

AOP는 @Aspect 어노테이션을 받아 만들 수 있는데,

이에 앞서 @SpringBootApplication 클래스에 @EnableAspectAutoProxy 어노테이션을 추가하자.

 

그리고 다음과 같이 AOP 클래스를 작성한다.

package com.example.demo.aop;

import java.util.Date;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class Aop {
	@Before("execution(* com.example.demo.controller.*.*(..))") //1
	public void before(JoinPoint joinPoint) { //2
		log.info((new Date()).toString() + " : " + " => before");
	}
	
	@After("execution(* com.example.demo.controller.*.*(..))")
	public void after(JoinPoint joinPoint) { //3
		log.info((new Date()).toString() + " : " + " => after");
	}

	@AfterReturning(pointcut="execution(* com.example.demo.controller.*.*(..))", returning="result")
	public void afterReturning(JoinPoint joinPoint, Object result) { //4
		log.info((new Date()).toString() + " : " + " => afterReturning");
	}

	@AfterThrowing(pointcut="execution(* com.example.demo.controller.*.*(..))", throwing="e")
	public void afterThrowing(JoinPoint joinPoint, Throwable e) { //5
		log.info((new Date()).toString() + " : " + " => afterThrowing");
	}
	
	@Around("@within(org.springframework.web.bind.annotation.RestController)") //6
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //7
		log.info((new Date()).toString() + " : " + " => around");
		
		Object result = joinPoint.proceed();
		return result;
	}
}

@Before : 메소드의 실행 전에 실행된다.

* com.example.demo.controller.*.*(..) : 맨 처음은 리턴, 그 이후는 {패키지}.클래스.메소드(args) 이다.

@After : 메소드의 실행 후에 실행된다.

@AfterReturning : 메소드의 실행이 종료되며 값이 리턴될 때 실행된다.

@AfterThrowing : 메소드의 실행 중 예외가 발생되면 실행된다.

@Around : 대상 메서드의 실행 전후에 실행되며 위의 것들을 종합한 것이다.

@within : 해당 어노테이션을 상속한 클래스들의 실행시 작동한다.

 

위의 @Around는 비단 하나의 어노테이션 뿐 아니라, "@within(...) || @within(...)" 등으로 여러 개의 어노테이션에서 실행될 수 있다.

 

 

 

 

 

호출 순

 

이제 위의 코드를 한 데 모아 출력하면 아래와 같은 결과값이 출력된다.

그리고 이를 토대로 필터, 인터셉터, aop의 호출 순서를 유추할 수 있다.

 

728x90
반응형

'실습 > 리눅스 서버 + 스프링 부트' 카테고리의 다른 글

Spring Boot 캐시 사용  (0) 2022.03.11
httpd (아파치 웹 서버)  (0) 2022.03.06
ntp를 이용한 타임서버 구축  (0) 2022.01.22
Scheduler  (0) 2022.01.15
JPA  (0) 2021.11.27