[Spring] Filter, Interceptor, AOP 차이 및 AOP를 사용하여 Logging을 구현한 이유

Jae Eon·2022년 2월 20일
7

백엔드 공부

목록 보기
16/17

업무를 하다 개인정보를 처리할 수 있는 비즈니스 로직에 접근하는 API에 대한 Logging이 필요해져 구현하기 위해 공부하고 정리한 포스트 입니다.
Logging은 크게 세가지 방식 [Filter, Interceptor, AOP] 을 사용 할 수 있다,
그 중 Spring AOP를 사용하여 처리 했는데 AOP를 사용한 이유와
나머지 방식의 차이에 대해 정리 하였음

🍎 Filter, Interceptor, AOP 차이

웹 개발을 하다 보면 실제 비즈니스 로직이 호출되기 이전, 이후에 공통적으로 처리해야 할 기능들이 존재하는데
대표적인 예로 Logging, 인증, 인가, 인코딩 변환 등등이 있다.

공통적인 기능의 코드를 모든 모듈 및 페이지에서 작성하게 되면 코드의 중복이 발생하게 되고 MSA 기반에서는 각 모듈마다 다른 코드가 작성되어 관리가 힘들 수 있다.

따라서 공통 기능을 모아서 처리 할 수 있는 방법으로 위 세가지가 사용된다.

하지만 이 세가지는 약간의 차이점이 존재하는데 우선 호출되는 시기가 다르다.

그림 참조 블로그 링크

간단히 설명 하면 Filter는 Dispatcher Servlet이 실행 되기전에 수행된다.

여기서 Dispatcher Servlet 이란 적합한 컨트롤러를 찾아 위임 해 주는 역할을 함.
(Ex. Get, Post 처리 ⭐️자세한 정리는 다음 블로그 링크 참조 Dispatcer Servlet 개념 및 동작 과정)

❄️ Filter - 일반적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할때 사용.

Filter는 Spring이 실행 되기전 실행되며 톰캣과 같은 웹컨테이너(WAS)에서 처리를 해주게 된다.
Request / Response에 대한 조작이 가능함
(HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 처리 함)

따라서 아래와 같은 처리에 적합하다.

  • 공통된 보안 및 인증/인가 관련 작업(XSS방어)
  • 이미지/데이터 압축 및 문자열 인코딩 변환 처리
  • Spring과 분리되어야 하는 기능
  • 모든 요청에 대한 로깅

❄️ Interceptor = 로그인 체크, 권한체크, 프로그램 실행시간 계산작업 로그확인 등의 작업

Interceptor는 Dispatcher Servlet에 N개 등록 될 수 있다. - DS가 해당 요청을 처리 가능한 Intercepter에게 할당
(Ex. 로그인 체크, 권한체크, 프로그램 실행시간 계산작업, 로그확인 Interceptor)

스프링의 모든 빈 객체에 접근 할 수있다.

따라서 아래와 같은 처리에 적합하다.

  • 세부적인 보안 및 인증/인가 공통 작업( 특정 사용자는 특정 기능을 사용 못하게 막음)
  • API 호출에 대한 로깅
  • Controller로 넘겨주는 정보(데이터)의 가공
    (필터와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체를 제공받으므로 객체 자체를 조작할 수는 없음,
    대신 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공 가능
    EX) JWT 토큰 정보를 파싱해서 컨트롤러에게 사용자의 정보를 제공하도록 가공)

❄️ Aop = 주로 '로깅', '트랜잭션', '에러 처리'등 비즈니스단의 메서드에서 조금 더 세밀하게 조정하고 싶을 때 사용 , Interceptor나 Filter와는 달리 메소드 전후의 지점에 자유롭게 설정이 가능하다.

Interceptor와 Filter는 주소(URL)로 대상을 구분해서 걸러내야하는 반면,
AOP는 주소, 파라미터, 애노테이션 등 PointCut이 지원하는 다양한 방법으로 대상을 지정할 수 있다.

즉 URL 기반이 아닌 PointCut 단위로 동작한다.
이 때문에 비즈니스 로직의 메서드 실행 전, 후 단위까지 핸들링 할 수 있다.

참고: https://goddaehee.tistory.com/154

🍓 AOP 방식으로 Logging을 구현한 이유

Logging 요구사항이 개인정보를 처리하는 api들에 대한 로그를 생성하는 것이었다.

따라서 모든 api에 대한 로그와 개인정보를 처리하는 로그를 구분할 필요가 있었고,
메소드 혹은 클래스단위의 처리가 필요해 보였으며 추후 모든 로그를 통합할때도 유연하게 확장을 할 수 있을 것이라고 생각하여 AOP를 선택해 구현 했다.

🍋 AOP 사용 방법

❄️ Gradle 라이브러리 적용하기

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-aop")
    }

❄️ 최상위 클래스에 @EnableAspectJAutoProxy 설정하기

main이 있는 최상위 클래스에 어노테이션을 등록해 AOP(@Aspect)를 찾을 수 있게 해준다.

@EnableAspectJAutoProxy
@SpringBootApplication
class LoggingApplication

fun main(args: Array<String>) {
    runApplication<LoggingApplication>(*args)
}

❄️ 공통기능 정의하기

아래 코드는 단순히 로그를 출력해 주는 기능의 코드다.

@Aspect
@Component
class LogAspect {
     val log: Logger = LoggerFactory.getLogger(LogAspect::class.java)
    
      @Before("bean(*Controller)")
      fun beforeLog(joinPoint: JoinPoint) {
      	log.info("이름이 Controller로 끝나는 모든 @Bean 호출 전 실행되는 로그 입니다.")
      }
      
      @After("execution(* com.miot2j.service.*.*(..))
      fun beforeLog(joinPoint: JoinPoint) {
      	log.info("service 패키지내의 모든 메소드 호출 후 실행되는 로그 입니다.")
      }
}

❄️ PointCut 설정하기

JoinPoint라는 개념을 설명하기 전에 기본적인 AOP의 개념을 설명 하겠다.

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것. 주로 부가기능을 모듈화함.
  • Target : Aspect를 적용하는 곳 (클래스, 메서드 .. )
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
  • PointCut : JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음

위 코드에서 알 수 있듯이 @Before("bean(*Controller)") 와 같이
PointCut을 이름이 Controller로 끝나는 모든 @Bean 호출 전 실행 으로 설정 할 수있다.

대표적인 PointCut 어노테이션은 다음과 같다

  • @Before: 대상 타겟의 호출 전
  • @After: 대상 타겟의 호출 후
  • @After-returning: 대상 타겟의 정상적인 호출 후
  • @After-throwing: 대상 타겟이 예외 발생 시, 예외발생 이후
    (@After - xxx 를 사용하면 좀더 세밀한 설정이 가능 해진다.)
  • @Around: 대상 타겟의 호출 전과 후

또한 포인트 컷 표현식을 사용할 수 있는데 여기선 @Bean, 인터페이스, 패키지 단위도 설정이 가능하다.


이미지 출처 링크

profile
🖋정리를 안하면 잊어버린다.👣한 발자국씩 가보자!

0개의 댓글