들어가며
먼저, AOP를 사용해야겠다라고 생각했던 이유는 Redis 캐싱을 적용해서, 게시글 상세 조회나 전체 목록 조회 등의 내용의 응답 시간을 단축 시켰는데, 실제로 구체적인 Request 로그로 확인해보고 싶었기에 해당 내용에 대해서 공부한 뒤 적용해봤다
(Redis 캐싱은 Caffeine + Redis 을 합친 2-Level 캐시로 구현했는데, 구현한 내용은 다른 포스팅에서 작성해보려한다)
AOP의 개념
먼저 AOP와 해당 기능을 사용했을때 장점에 대해서 얘기해보려 한다

AOP란 ?
관점(Aspect)지향 프로그래밍으로, 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍이다.
여기서 관점은, 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것을 의미한다
즉, 정리하자면 비즈니스 핵심적인 기능은 아닐 수도 있지만, 애플리케이션 코드가 중복되고 강력하게 결합되어 있어 다른 로직과 분리할 수 없는 애플리케이션 로직을 의미하여, 로깅, 보안, 트랜잭션 처리 등등에 사용된다고 이해해주면 될거같다
AOP의 장점
일단 앞서 설명했듯이 나눠져 있는 관심 사항 ? 구현 사항이 하나의 장소로 응집될 수 있어서 중복되는 코드를 줄일 수 있다. 그리고 해당 로직은 심사에 대한 코드만 포함하기 때문에 코드가 깔끔해지고 유지보수가 용이해진다
결론적으로 난 AOP가 객체지향적으로 코드를 설계할 수 있도록 도와주는 역할을 한다고 생각했다
AOP 관련된 내용도 잠깐 찾아보긴 했는데 엄청 깊고, 내용도 많아서 아래 공식문서를 참고해주면 더 이해가 빠를거같다
https://docs.spring.io/spring-framework/reference/core/aop.html
Aspect Oriented Programming with Spring :: Spring Framework
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enabl
docs.spring.io
본론
위에서도 다뤘듯이, 결론적으로 Spring AOP는 로깅과 같은 작업들을 함께 모와서 처리해줄 수 있다. 난 해당 기능을 활용해서 API Request에 대한 로그를 출력하는 공통 클래스를 만들어보았는데, 이제 해당 내용을 깊게 다루어보겠다
의존성 추가
먼저 아래의 AOP 의존성을 추가해주었다
implementation 'org.springframework.boot:spring-boot-starter-aop'
여기서 org.json 패키지는 JDK에도, Spring Boot 스타터에도 기본 포함되어 있지 않아서, 아래 사진처럼 import 오류가 발생한다면

아래 한줄도 추가해주면 따로 내려받을 수 있다
implementation 'org.json:json:20230227'
LogAspect 구현
이제 기본 세팅은 끝났고 실제로 LogAspect 클래스를 구현한 , 해당 클래스를 메서드 별로 나눠보면서 얘기해보려고 한다
LogAspect 적용할 패키지 설정
@Pointcut("execution(* com.sopt.DaisoMall..service..*(..))")
public void businessMethods() {}
@Pointcut("execution(* com.sopt.DaisoMall..controller..*(..))")
public void controllerMethods() {}
일단 먼저 businessMethods과 controllerMethods 메서드를 구현하여 모든 서비스 계층과 모든 컨트롤러 계층의 요청에 적용을 해주었다. 해당 내용은 정규 표현식이라서, 아래 내용처럼 global에 있는 로직들이나, 특정 로직들은 제외할 수도 있다
!execution(* com.sopt.DaisoMall.global..*(..))"
메서드 소요시간 측정
joinPoint.proceed()는, 비즈니스 메서드의 실행을 제어하는데,
joinPoint.proceed() 호출 전은 비즈니스 메서드 호출 전이고, joinPoint.proceed() 호출 후는 비즈니스 메서드 호출 후 이므로 해당 메서드를 이용해 소요시간을 측정할 수 있다
@Around("businessMethods()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long elapsed = System.currentTimeMillis() - start;
log.info("▶ {} executed in {} ms",
joinPoint.getSignature(), elapsed);
}
}
Controller 진입 시 HTTP 요청 정보 + 핸들러 메서드, 파라미터 로깅
@Around("controllerMethods()")
public Object logControllerRequest(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attrs =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) {
HttpServletRequest req = attrs.getRequest();
String uri = URLDecoder.decode(req.getRequestURI(), StandardCharsets.UTF_8);
String httpMethod= req.getMethod();
JSONObject params= extractParams(req);
log.info("[{}] {}", httpMethod, uri);
log.info(" handler = {}.{}",
joinPoint.getSignature().getDeclaringType().getSimpleName(),
joinPoint.getSignature().getName());
log.info(" params = {}", params.toString());
}
return joinPoint.proceed();
}
요청 URI, 헤더 정보, 쿼리 파라미터 등의 정보는 시그니처에 담겨있지 않은 정보다. 그래서 RequestContextHolder의 getRequestAttributes 메서드를 사용하여 해당 정보들을 가져올 수 있었다
또 URLDecoder.decode()를 이용해서 URI를 UTF-8로 디코딩하여 한글 등을 입력받아 올때, 생길 수 있는 이슈들을 방지했다
쿼리 파라미터를 JSONObject 로 변환
private JSONObject extractParams(HttpServletRequest request) {
JSONObject json = new JSONObject();
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String safe = name.replaceAll("\\.", "_");
json.put(safe, request.getParameter(name));
}
return json;
}
마지막으로 이전 메서드에서 HttpServletRequest.getParameterNames로 전달된, 각 파라미터 이름을 키로 호출 결과를 값으로 JSONObject에 담아 반환해주었다
이때 String safe = name.replaceAll("\\.", "_"); 을 통해서 key 에 점(.)이 들어올 경우 언더스코어(_)로 대체해서 발생할 수 있는 예외케이스도 함께 정상적으로 반환될 수 있도록 설정했다
실제 Request 로그 반환 내용




'Spring > AOP' 카테고리의 다른 글
| AOP 기반 Lock 인터페이스 추상화, Redisson 분산락 적용 동시성 처리(Spring) (1) | 2025.06.01 |
|---|