@RestControllerAdvice
public class ExceptionController {
@ExceptionHandler({SendFailedException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public MessageDto sendFail() {
return new MessageDto("이메일 전송에 실패했습니다.");
}
@ExceptionHandler(NotFoundEntityException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public MessageDto notFound(NotFoundEntityException ex) {
String message = ex.getMessage();
if (message == null) {
message = "존재하지 않습니다.";
}
return new MessageDto(message);
}
@ExceptionHandler({AccessDeniedException.class})
@ResponseStatus(HttpStatus.FORBIDDEN)
public MessageDto notAllowed(AccessDeniedException ex) {
return new MessageDto(ex.getMessage());
}
}
클래스가 선언된 패키지 혹은 그 하위 패키지에서 발생하는 예외들은 이 클래스에서 자동으로 잡히고 처리된다.
따라서 각각의 컨트롤러에서 별도로 예외 처리를 하지 않아도 된다. 이렇게 하면 예외 처리 로직이 중복되는 것을 방지하고, 코드의 가독성을 높일 수 있다.
AOP는 흩어진 관심사를 한 곳에서 관리하도록 하는 프로그래밍 패러다임이다.
로깅, 트랜잭션 관리, 보안 등과 같이 여러 곳에서 공통적으로 사용되는 코드를 분리하여 관리하는데 유용하게 사용된다.
@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);
}
}
}
}```
AOP(Aspect-Oriented Programming)를 사용하여 API의 실행 시간을 측정하고, 이를 데이터베이스에 저장하는 기능을 수행하는 것이다.
@Aspect: 해당 클래스가 AOP를 구현하는 클래스임을 명시하는 어노테이션이다.
@Component: 스프링이 관리하는 빈 클래스임을 명시하는 어노테이션이다.
@Pointcut: AOP의 대상이 되는 메소드를 지정하는 어노테이션이다. "execution( com.sparta.myselectshop.controller.ProductController.(..))"는 ProductController의 모든 메소드를 대상으로 한다.
@Around: @Pointcut으로 지정된 메소드를 실행하기 전후로 로직을 실행하도록 하는 어노테이션이다.
execute(ProceedingJoinPoint joinPoint): @Around 어노테이션에 의해 실행되는 메소드이다. joinPoint.proceed()를 호출하여 대상 메소드를 실행하고, 그 전후로 원하는 로직(여기서는 시간 측정 및 데이터베이스 저장)을 추가한다.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();: 현재 접근하려는 사용자의 인증 정보를 가져온다.
if (auth != null && auth.getPrincipal().getClass() == UserDetailsImpl.class): 인증 정보가 존재하고, 인증된 사용자가 UserDetailsImpl 클래스의 인스턴스인 경우에만 로직을 수행한다.
ApiUseTime apiUseTime = apiUseTimeRepository.findByUser(loginUser).orElse(null);: 인증된 사용자의 API 사용 시간을 데이터베이스에서 찾습니다. 없다면 null을 반환한다.