AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 ` 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.
AOP를 활용하면 공통된 기능을 여러 곳에 중복해서 작성하지 않고도 쉽게 관리할 수 있어 애플리케이션의 유지보수성을 크게 향상시킬 수 있습니다.

AOP를 활용하면 공통된 기능을 여러 곳에 중복해서 작성하지 않고도 쉽게 관리할 수 있어 애플리케이션의 유지보수성을 크게 향상시킬 수 있습니다.
횡단 관심사를 모듈화한 것. 주로 공통 기능을 정의합니다.
어드바이스가 적용될 수 있는 지점, 예를 들어 메서드 호출이나 예외 발생 시점.
횡단 관심사 코드를 실제로 구현한 것. 언제(예: 메서드 호출 전후, 예외 발생 시 등) Aspect의 기능을 적용할지 정의합니다.
2. 어드바이스 종류
- @Around: '핵심기능' 수행 전과 후 (@Before + @After) - @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행) - @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작) - @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능) - @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)
특정 Join Point를 선택하는 표현식. 어느 지점에 Advice를 적용할지를 정의합니다.
- 포인트컷 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.myselectshop.controller..(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }- modifiers-pattern
- public, private, *
- return-type-pattern
- void, String, List, *
- declaring-type-pattern
- 클래스명 (패키지명 필요)
- com.sparta.myselectshop.controller.* - controller 패키지의 모든 클래스에 적용
- com.sparta.myselectshop.controller.. - controller 패키지 및 하위 패키지의 모든 클래스에 적용
- method-name-pattern(param-pattern)
- 함수명
- addFolders : addFolders() 함수에만 적용
- add* : add 로 시작하는 모든 함수에 적용
- 파라미터 패턴 (param-pattern)
- (com.sparta.myselectshop.dto.FolderRequestDto) - FolderRequestDto 인수 (arguments) 만 적용
- () - 인수 없음
- (*) - 인수 1개 (타입 상관없음)
- (..) - 인수 0~N개 (타입 상관없음)
Aspect와 비즈니스 로직을 엮는 과정. 컴파일 시점, 클래스 로딩 시점, 런타임 시점에 이루어질 수 있습니다.
@Slf4j(topic = "UseTimeAop")
@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 {
// 핵심기능 수행
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();
// API 사용시간 및 DB 에 기록
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser).orElse(null);
if (apiUseTime == null) {
// 로그인 회원의 기록이 없으면
apiUseTime = new ApiUseTime(loginUser, runTime);
} else {
// 로그인 회원의 기록이 이미 있으면
apiUseTime.addUseTime(runTime);
}
log.info("[API Use Time] Username: " + loginUser.getUsername()
+ ", Total Time: " + apiUseTime.getTotalTime() + " ms");
apiUseTimeRepository.save(apiUseTime);
}
}
}
}


Spring이 프록시(가짜 혹은 대리) 객체를 중간에 삽입해줍니다.
DispatcherServlet 과 ProductController 입장에서는 변화가 전혀 없습니다.
스프링부트 테스트와 AOP에 대해 공부했다 개념과 사용법을 이해하고 예제를 통해 실습해봤다. 앞으로도 더많은 실습을 통해 내껄로 만들어야겠다.