hanker

[SPRING] 웹 서비스에서 발생하는 이벤트를 DB에 기록하기 (AOP) 본문

SPRING

[SPRING] 웹 서비스에서 발생하는 이벤트를 DB에 기록하기 (AOP)

hanker 2025. 6. 6. 04:39
반응형

Spring AOP를 사용하여 웹 서비스에서 이벤트가 발생할 때 DB에 기록하는 로직을 만들어보자.

 


1. 설정

 

우선 AOP를 사용하기 위해 의존성을 추가해줘야 한다.

pom.xml 

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

 

application.properties

spring.aop.auto=true
spring.aop.proxy-target-class=true

 

spring.aop.auto

  • true: @EnableAspectJAutoProxy를 자동으로 활성화
  • false: AOP 자동 설정 비활성화, 수동으로 설정해야 함
  • 기본값: true (Spring Boot에서는 보통 생략 가능)

spring.aop.proxy-target-class

Spring AOP가 객체의 프록시를 만드는 방식을 설정

  • true : CGLIB 프록시 
  • false : JDK 동적 프록시

2. AOP Aspect 클래스 정의
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
@RequiredArgsConstructor
public class EventAspect {
    private final ObjectMapper objectMapper;

    // @LogEvent 어노테이션이 있는 메서드에 대해 적용
    @Around("@annotation(logEvent)")
    public Object logAnnotatedMethods(ProceedingJoinPoint joinPoint, LogEvent logEvent) throws Throwable {
        return executeWithLogging(joinPoint, logEvent.eventType());
    }

    private Object executeWithLogging(ProceedingJoinPoint joinPoint, String eventType) throws Throwable {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

        long startTime = System.currentTimeMillis();

        // 메서드 정보 추출
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();

        // 이벤트 로그를 담을 Map 생성
        // Entity 클래스에 담아서 전송해도 무관
        Map<String, Object> map = new HashMap<>();
        map.put("eventType", eventType);
        map.put("methodName", methodName);
        map.put("className", className);

        try {
            // 파라미터 정보 추출
            Object[] args = joinPoint.getArgs();
            if (args.length > 0) {
                map.put("parameters", objectMapper.writeValueAsString(args));
            }

            // IP 주소 추출
            map.put("ipAddress", request.getRemoteAddr());

            // 메서드 실행
            Object result = joinPoint.proceed();

            // 실행 시간 계산
            long executionTime = System.currentTimeMillis() - startTime;
            map.put("executionTime", executionTime);

            // 로그 저장() Repository에 맵을 담아서 전달해준다
            // aopRepository.saveEventLog(map);
            System.out.println("map = " + map);
            System.out.println("저장되었습니다.");

            return result;

        } catch (Exception e) {
            throw e;
        }
    }
}

 


3. Custom 어노테이션 정의
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogEvent {
    String eventType() default "DEFAULT";
    boolean includeParameters() default true;
    boolean includeResponse() default false;
}

 

 


4. Sample RestController 정의
@RestController
@RequestMapping("/api/users")
public class UserController {

    private List<User> users = new ArrayList<>(List.of(
            new User(1, "hanker", "hanker@example.com"),
            new User(2, "Hong gil dong", "gildong@example.com")
    ));

    // GET: 특정 사용자 조회
    @GetMapping("/{id}")
    @LogEvent(eventType = "GET_USER_BY_ID", includeParameters = true, includeResponse = true)
    public User getUserById(@PathVariable int id) {
        return users.stream()
                .filter(user -> user.getId() == id)
                .findFirst()
                .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

 


실행

 

호출 URL

 

저장 결과값

 

반응형