'핵심기능': 각 API 별 수행해야 할 비즈니스 로직
'부가기능': 핵심기능을 보조하는 기능
문제점
AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.
AOP 설정, Spring 빈(Bean) 클래스에만 적용 가능
적용 위치 지정
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 { ... }
public
, private
, *
void
, String
, List<String>
, *****
com.sparta.myselectshop.controller.*
: controller 패키지의 모든 클래스에 적용com.sparta.myselectshop.controller..
: controller 패키지 및 하위 패키지의 모든 클래스에 적용addFolders
: addFolders() 함수에만 적용add*
: add 로 시작하는 모든 함수에 적용(com.sparta.myselectshop.dto.FolderRequestDto)
: FolderRequestDto 인수 (arguments) 만 적용()
: 인수 없음(*)
: 인수 1개 (타입 상관없음)(..)
: 인수 0~N개 (타입 상관없음)@Component
@Aspect
public class Aspect {
@Pointcut("execution(* com.sparta.myselectshop.controller.*.*(..))")
private void forAllController() {}
@Pointcut("execution(String com.sparta.myselectshop.controller.*.*())")
private void forAllViewController() {}
@Around("forAllContorller() && !forAllViewController()")
public void saveRestApiLog() {
...
}
@Around("forAllContorller()")
public void saveAllApiLog() {
...
}
}
@Slf4j(topic = "UseTimeAop")
@Aspect // aop 설정(빈에만 적용 가능)
@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);
}
}
}
}
개념적 이해
스프링 실제 동작
시퀀스 다이어그램 (Sequence Diagram)
Spring이 프록시(가짜 혹은 대리) 객체를 중간에 삽입해준다.
DispatcherServlet 과 ProductController 입장에서는 변화가 전혀 없다.
joinPoint.proceed()
에 의해서 원래 호출하려고 했던 함수, 인수(argument) 가 전달된다.