Spring/팀스파르타

49. Spring AOP

열심히 해 2024. 12. 6. 14:57

 

 

  1. @Aspect: AOP 기능을 사용할 것임을 알리기
    • Spring 빈(Bean) 클래스에만 적용 가능합니다.

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

  3. 포인트컷: 어느 기능에  적용할지 알리기
    • 포인트컷 Expression Language

형태

execution(modifiers-pattern? return-type-pattern declaring-type-pattern?
method-name-pattern(param-pattern) throws-pattern?)

 

 

예시

@Around("execution(public * com.sparta.myselectshop.controller..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }

 

  • ? 는 생략 가능
  • modifiers-pattern
    : public, private, *

  • return-type-pattern
    : void, String, List<String>, *****

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

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

 

  • @Pointcut
    • 포인트컷 재사용 가능
    • 포인트컷 결합 (combine) 가능

 

 

@Aspect
@Component
@RequiredArgsConstructor
public class UseTimeAop {

    private final ApiUseTimeRepository apiUseTimeRepository;

    @Pointcut("execution(* com.sparta.myselectshop.controller.ProductController.*(..))")
    private void product() {}
    @Pointcut("execution(* com.sparta.myselectshop.controller.FolderController.*(..))")
    private void folder() {}
    @Pointcut("execution(* com.sparta.myselectshop.naver.controller.NaverApiController.*(..))")
    private void naver() {}

    @Around("product() || folder() || naver()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        try {
            // 핵심 기능 수행, 여기서는 Controller의 해당 메서드를 수행시킨다.
            Object output = joinPoint.proceed(); 
            return output;
        } finally {
            long endTime = System.currentTimeMillis();
            long runTime = endTime - startTime;

            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth != null && auth.getPrincipal().getClass() == UserDetailsImpl.class) {
                UserDetailsImpl userDetails = (UserDetailsImpl) auth.getPrincipal();
                User loginUser = userDetails.getUser();

                ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser).orElse(null);
                if (apiUseTime == null) {
                    apiUseTime = new ApiUseTime(loginUser, runTime);
                } else {
                    apiUseTime.addUseTime(runTime);
                }

                apiUseTimeRepository.save(apiUseTime);
            }
        }
    }
}