220716_TIL : AOP / 스프링 AOP

백승한·2022년 7월 16일
0

AOP란?

부가기능 모듈화의 필요성

  • '핵심기능': 각 API 별 수행해야 할 비즈니스 로직
    ex) 상품 키워드 검색, 관심상품 등록, 회원 가입, 관심상품에 폴더 추가, ....

  • '부가기능': 핵심기능을 보조하는 기능
    ex) 회원 패턴 분석을 위한 로그 기록, API 수행시간 저장

// 측정 시작 시간
long startTime = System.currentTimeMillis();

try {
	// 핵심기능 수행
	// 로그인 되어 있는 회원 테이블의 ID
	Long userId = userDetails.getUser().getId();
	
	Product product = productService.createProduct(requestDto, userId);
	
	// 응답 보내기
	return product;
} finally {
	// 측정 종료 시간
	long endTime = System.currentTimeMillis();
	// 수행시간 = 종료 시간 - 시작 시간
	long runTime = endTime - startTime;
	// 수행시간을 DB 에 기록
	...
}
  • 문제점
    • 모든 '핵심기능'의 Controller 에 '부가기능' 코드를 추가했을 때..
      • '핵심기능' 이 100개라면??
        • 100개의 '핵심기능' 모두에 동일한 내용의 코드 추가 필요
      • '핵심기능' 이 나중에 추가된다면?
        • 항상 '부가기능' 추가를 신경써야 함
        • '부가기능' 추가를 깜박한다면?
          • 일부 API 수행시간이 추가되지 않음 → Top5 회원의 신뢰성 이슈
    • '핵심기능' 수정 시
      • 같은 함수 내에 '핵심기능'과 '부가기능'이 섞여 있음
      • '핵심기능' 이해를 위해 '부가기능'까지 이해 필요
    • '부가기능'의 변경이 필요하다면??
      • '핵심기능'의 개수만큼 '부가기능'도 수정해 줘야 함
      • '부가기능' 삭제

위 문제점들 외에 여러가지 단점들을 보완하기 위해 AOP를 통해 부가기능을 모듈화를 했다.

부가기능을 모듈화

  • AOP
    서로 다른 관점으로 핵심기능은 핵심끼리, 부가기능은 부가끼리 설계하고 구현하자! 이런 생각에서 나오게 된 프로그래밍
  • AOP (Aspect Oriented Programming) 를 통해 부가기능을 모듈화
    • '부가기능'은 '핵심기능'과는 관점(Aspect), 관심이 다름
    • 따라서 '핵심기능'과 또옥~!! 분리해서 '부가기능' 중심으로 설계, 구현 가능

스프링이 제공하는 AOP

  • AOP 용어
  1. 어드바이스: 부가기능
  2. 포인트컷: 부가기능 적용위치

스프링 AOP

스프링 AOP 동작 이해

  • 개념적 이해

  • 스프링 실제 동작

핵심기능을 호출하는 어떤 녀석이 있는데, 그 녀석이 핵심기능을 바로 호출하는 것이 아니라 중간에 프록시라는 객체가 생성이 되고, 프록시가 핵심기능을 호출하도록 한다. 프록시는 결국은 부가기능의 역할을 한다.

  • 시퀀스 다이어그램 (Sequence Diagram)

    • AOP 적용 전

    • AOP 적용 후

      디스패처서블릿이 /api/products에 해당되는 함수를 찾아서 Controller에 요청을 보낼 때, AOP Proxy가 부가기능을 하기 위해서 Controller인 척 하면서 중간에서 가로챈다.
      중요한건 디스패처서블릿은 마치 Controller로 호출할 때랑 동일하게 호출하고, 동일하게 결과를 받는다는 것이다.

    • DispatcherServlet 과 ProductController 입장에서는 변화가 전혀 없음
      • 호출되는 함수의 input, output 이 완전 동일

      • "joinPoint.proceed()" 에 의해서 원래 호출하려고 했던 함수, 인수(argument) 가 전달됨

        → createProduct(requestDto)

  • 스프링 서버가 기동될 때

    • 핵심 기능 DI 시
    • 프록시 객체를 중간에 삽입

스프링 AOP 어노테이션

  1. @Aspect

    • 스프링 빈 (Bean) 클래스에만 적용 가능
  2. 어드바이스 종류

    • @Around: '핵심기능' 수행 전과 후 (@Before + @After)
    • @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)
    • @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
    • @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)
    • @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)
  3. 포인트컷

    • 포인트컷 Expression Language
      • 포인트컷 Expression 형태 ( ?는 생략 가능 )
        execution(modifiers-pattern? return-type-pattern declaring-type-pattern? 
        method-name-pattern(param-pattern) throws-pattern?)
    • 포인트컷 Expression 예제
    @Around("execution(public * com.sparta.springcore.controller..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }
    • modifiers-pattern

      • public, private, *
    • return-type-pattern

      • void, String, List<String>, *
    • declaring-type-pattern

      • 클래스명 (패키지명 필요)
      • com.sparta.springcore.controller.* - controller 패키지의 모든 클래스에 적용
      • com.sparta.springcore.controller.. - controller 패키지 및 하위 패키지의 모든 클래스
    • method-name-pattern(param-pattern)

      • 함수명
        • addFolders : addFolders() 함수에만 적용
        • add* : add 로 시작하는 모든 함수에 적용
      • 파라미터 패턴 (param-pattern)
        • (com.sparta.springcore.dto.FolderRequestDto) - FolderRequestDto 인수 (arguments) 만 적용
        • () - 인수 없음
        • (*) - 인수 1개 (타입 상관없음)
        • (..) - 인수 0~N개 (타입 상관없음)
    • @Pointcut

      • 포인트컷 재사용 가능

      • 포인트컷 결합 (combine) 가능

        @Component
        @Aspect
        public class Aspect {
          @Pointcut("execution(* com.sparta.springcore.controller.*.*(..))")
          private void forAllController() {}
        
          @Pointcut("execution(String com.sparta.springcore.controller.*.*())")
          private void forAllViewController() {}
        
          @Around("forAllContorller() && !forAllViewController")
          public void saveRestApiLog() {
              ...
          }
        
          @Around("forAllContorller()")
          public void saveAllApiLog() {
              ...
          }	
        }
  • Controller - Service - Repository 3계층에 맞춰 구현을 해야 하는 다른 이유
    - Controller 에 비즈니스 로직을 추가한다면?
    - Controller 에서 Repository 를 바로 호출한다면??
    Service가 아무리 Repository를 바로 호출하더라도 Controller에서 Repository를 바로 호출하지 말아야한다. 그래야 AOP라는 장점을 사용할 수 있다.
    만약 모든 Service에 AOP를 적용하도록 구현해 놓았다면 부가 기능이 적용되지 않을 것이다.

기록

이제 얼마 후에 실전 프로젝트가 시작하는데, 느낌적으로 스프링 AOP를 활용하게 될 것 같다.
잘 기억해두었다가 멋지게 사용해보면 좋겠다.

profile
방문해주셔서 감사합니다🙂

0개의 댓글