Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

이지은님의 블로그

250311 - Java Spring 심화: AOP의 개념과 동작과정 및 interceptor와 비교(@Aspect, @Around, ProceedingJoinPoint) 본문

TIL

250311 - Java Spring 심화: AOP의 개념과 동작과정 및 interceptor와 비교(@Aspect, @Around, ProceedingJoinPoint)

queenriwon3 2025. 3. 11. 17:13

 오늘 배운 

세션과 JPA 심화공부를 하면서 몰랐던 부분을 따로 정리하고자 한다.

 

<<목차>>

1. Spring AOP

    1) AOP(Aspect Oriented  Programming)

    2) AOP의 주요개념

    3) AOP 동작과정

    4) Spring AOP 구현(AspectJ)

    5) Interceptor와 비교 정리

2. Spring AOP 실습

    1) 사용권한 접근 제어 AOP

    2) 로깅 AOP

 


 

 

 

1. Spring AOP

1) AOP(Aspect Oriented  Programming)

: 관점 지향 프로그래밍

AOP는 핵심적인 비즈니스 로직으로부터 ‘횡단 관심사’를 분리하는 것

어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화

==> 부가기능을 따로 관리하는 것

 

횡단 관심사란?
애플리케이션에서 코드가 중복되고, 강력하게 결합되어있어 다른 로직과 분리할 수 없는 애플리케이션 로직
Ex) 로깅, 보안, 트랜잭션 등등

==> 이 부가기능을 따로 관리해주는 것이 AOP이다.

 

 

장점1: 전체 코드 기반에 흩어져있는 관심사항이 하나의 장소로 응집

장접2: 자신의 주요 관심사항에 대한 코드만 포함하고 있기에 코드가 깔끔

==> 객체 자향적으로 코드를 짤 수 있게 도우며, 유지보수가 용이

 

 

2) AOP의 주요개념

1️⃣ Aspect: 관심사를 모듈화한 것

2️⃣ Target: 부가기능을 부여할 대상(ex) service)

3️⃣ Advice: 타깃에게 제공할 부가기능을 담은 모듈(무슨 기능과 어느 시점에 주입해줄건지)

4️⃣ Join Point: 어드바이스가 적용될 수 있는 위치, 끼어들 수 있는 지점

5️⃣ Pointcut: 어드바이스를 적용할 조인 포인트를 선별하는 작업(어떤 메서드에 적용할건지)

6️⃣ Advisor: 포인트컷과 어드바이스를 하나씩 갖고있는 오브잭트

7️⃣ Weaving: 조인포인트에 어드바이스를 적용하는 방법, 실제 코드에 AOP 기능을 결합하는 과정

 

 

3) AOP 동작과정

  • BeanPostProcessor(빈 후처리기): 생성된 빈 객체를 스프링 컨테이너에 등록하기 전에 조작하는 객체
  • 프록시: 원본 객체를 감싼 새로운 객체, 스프링 AOP에서 부가기능을 추가할때 사용하는 패턴
  • (프록시 패턴: 클라이언트가 객체를 직접 호출하지 않고, 프록시를 호출하면서 제어권을 가지는 패턴)

1️⃣ 빈 객체를 생성한 뒤 빈 후처리기에 전달한다.

2️⃣ 어드바이저 내의 포인트 컷을 이용해 전달받은 빈이 프록시 적용대상인지 확인한다.

3️⃣ 프록시 생성 대상 빈들을 대상으로 프록시를 생성한다.

4️⃣ 프록시를 생성한 빈이라면 프록시 객체를, 프록시를 생성하지 않는 빈이라면 그냥 빈을 반환한다.

5️⃣ 빈 후처리기에게 전달받은 객체를 컨테이너의 빈으로 등록한다.

 

 

4) Spring AOP 구현(AspectJ)

1️⃣ 의존성 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
}

 

 

2️⃣ AspectJ 어드바이스(어느시점에…)

  • @Aspect: 해당클래스를 Aspect로 사용하겠다는 것을 명시
  • @After: 메서드가 반환되거나 예외 상황이 발생한 이후에 호출(finally)
  • @AfterReturning: 메서드가 반환된 이후에 호출(try)
  • @AfterThrowing: 메서드가 예외상황을 발생시킨 이후에 호출(catch)
  • @Before: 메서드가 호출되기 이전에 호출
  • @Around: 메서드의 호출 전과 반환되거나 예외 상황 이후에 호출

 

3️⃣ AspectJ 포인트컷 표현식

execution([(1) 접근 제한자 패턴] (2) 타입패턴 [(3) 타입패턴..] (4) 이름패턴 (5)(타입패턴) (6)[throws 예외 패턴])

 

(1) 접근 제한자 패턴: public이나 private등 접근제한자.(생략가능)

(2) 타입 패턴: 리턴값의 타입 패턴

(3) 타입 패턴: 패키지와 클래스의 이름에 대한 패턴(생략가능)

(4) 이름 패턴: 메서드 이름 패턴

(5) 타입 패턴: 파라미터의 타입패턴을 순서대로 넣을 수 있다.(와일드 카드 사용)

(6) 예외 패턴: 예외이름 패턴이다.(생략가능)

 

execution(int minus(int, int))
execution(* minus(int, int)) // 반환타입이 상관 없어짐
execution(* minus(..)) // 파라미터 타입이 상관 없어짐
execution(* *(..)) // 이름을 와일드 카드로 지정 -> 모든 메서드

표현식의 응용은 다음과 같이 가능하다. 처음에는 생략가능한 메서드 요소를 생략하고 리턴값 타입, 메서드 이름, 파라미터 타입을 작성해서 포인트 것에 포함되도록 했다.

하지만 와일드 카드를 사용해서, 리턴값 타입, 파라미터 타입, 메서드 이름을 지정하면서 다음과 같이 간단히 포인트컷 표현식을 사용할 수 있다.

 

혹은 경로지정 방식말고 특정 어노테이션이 붙은 포인트에 AOP 실행할 수도 있다.

@Around("@annotation(com.example.deliveryappproject.config.aop.annotation.LogTrace)")

 

또는 스프링 빈의 모든 메서드에 적용하고 싶다면 다음과 같이 적용하면 된다.

@Around("bean(simpleEventService)")

 

 

 

4️⃣ joinPoint 인터페이스의 메서드

getArgs() 대상 메서드의 인자 목록을 반환합니다.
getSignature() 대상 메서드의 정보를 반환합니다.
getSourceLocation() 대상 메서드가 선언된 위치를 반환합니다.
getKind() Advice 종류를 반환합니다.
getStaticPart() Advice 실행될 JoinPoint 정적 정보를 반환합니다.
getThis() 대상 객체를 반환합니다.
getTarget() 대상 객체를 반환합니다.
toString() JoinPoint 정보를 문자열로 반환합니다.
toShortString() JoinPoint 간단한 정보를 문자열로 반환합니다.
toLongString() JoinPoint 자세한 정보를 문자열로 반환합니다.

 

 

 

5️⃣ 사용예제

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    // 실행 전 com.example.service 패키지의 모든 메서드에 적용
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Method 실행 전: " + joinPoint.getSignature().getName());
    }

    // 실행 후(예외 발생시 제외) com.example.service 패키지의 모든 메서드에 적용
    @AfterReturning("execution(* com.example.service.*.*(..))")
    public void logAfterReturning(JoinPoint joinPoint) {
        System.out.println("Method 실행 후: " + joinPoint.getSignature().getName());
    }
}

 

 

5) Interceptor 비교 정리

비교 항목 AOP Interceptor
사용 목적 횡단 관심사 적용 (로깅, 트랜잭션) HTTP 요청 전후 처리 (인증, 권한)
적용 범위 Spring Bean (서비스, 리포지토리 ) Spring MVC 요청 처리 과정
실행 위치 메서드 실행 전후, 예외 발생 요청(request) /, 응답 처리
주요 메서드 @Before, @After, @Around preHandle(), postHandle(), afterCompletion()
주요 용도 로깅, 트랜잭션, 성능 측정 인증, 권한 체크, 요청 로깅
등록 방식 @Aspect + @Component WebMvcConfigurer 사용하여 addInterceptors() 등록
적용 대상 모든 Spring Bean에서 사용 가능 컨트롤러 요청에 대해서만 동작

 

 

 

 

2. Spring AOP 실습

1) 사용권한 접근 제어 AOP

다음과 같은 가게 설정 api 가게 사장님 역할만 사용할 있다. 따라서 권한에 대한 접근을 aop 제한하고자 한다.

 

 

 

먼저 포인트컷에 대해 작성하는 방법은 생각보다 많다는 것을 알게되었다. 

// 어노테이션에서 경로를 지정하는 방법
@Around("@annotation(com.example.deliveryappproject.config.aop.annotation.LogTrace)")
public Object checkPermission(ProceedingJoinPoint joinPoint) {
    ...
}

// authPermission 과 매개변수 AuthPermission authPermission 매핑하는 방법
@Around("@annotation(authPermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint, AuthPermission authPermission) {
    ...
}

// 서비스에 적용시 서비스레이어 빈을 지정하는 방법
@Around("bean(MenuCategoryMenuService)")

// Pointcut 어노테이션으로 메서드 생성하여 Around 파라미터로 지정
@Pointcut("@annotation(com.example.deliveryappproject.config.aop.annotation.LogTrace)")
public void loggerPointcut() {
}

@Around("loggerPointcut()")
public Object doLogTrace(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    ...
}

// Pointcut 어노테이션 + excution로 메서드 경로 및 표현식 설정
@Pointcut("execution(* org.example.expert.domain.comment.controller.CommentAdminController.deleteComment(..))")
private void commentAdminControllerPointcut() {}

@Pointcut("execution(* org.example.expert.domain.user.controller.UserAdminController.changeUserRole(..))")
private void userAdminController() {}

@Around("commentAdminControllerPointcut() || userAdminController()")
public Object logAdminAccess(ProceedingJoinPoint joinPoint) throws Throwable {
    ...
}

이 외에도 @Before 또는 @After에 서는 JoinPoint 타입 매개변수를 사용할 수도 있다.

 

사용권한에 대한 aop 작성 예제
@Aspect
@Component
public class AuthPermissionAspect {

    // authPermission 과 매개변수 AuthPermission authPermission 매핑하는 방법
    @Around("@annotation(authPermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, AuthPermission authPermission) throws Throwable {

        AuthUser authUser = (AuthUser) joinPoint.getArgs()[0];
        // joinPoint의 첫번째 인자를 가져옴
        // ProceedingJoinPoint: 메서드 실행 정보를 담고 있는 객체
        // joinPoint.getArgs(): joinPoint의 첫번째 매개변수를 가져옴 (AuthUser를 사용해야함)
        
        if (!authUser.getUserRole().equals(authPermission.role())) {
            throw new ForbiddenException("사용 권한이 없습니다.");
        }

        // aop 실행 이후 메서드 실행
        // joinPoint.proceed()이후 실행 내용이 없다면 @Before을 사용해도 됨
        return joinPoint.proceed();
    }
}

 

 

 

2) 로깅 AOP

각 요청과 응답에대한 로그를 출력하는 AOP를 작성해보고자한다. 사용자 정보와 기타 요청에 대한 정보값을 가져오기 위해서는 HttpServletRequset가 필요하다.

@Around("commentAdminControllerPointcut() || userAdminController()")
public Object logAdminAccess(ProceedingJoinPoint joinPoint) throws Throwable {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    Long userId = (Long) request.getAttribute("userId");
    // 로깅 로직
    
    return joinPoint.proceed();
}

 

RequestContextHolder.getRequestAttributes()

현재 HTTP 요청의 컨텍스트 정보를 가져옴

RequestContextHolder는 Spring이 HTTP 요청 정보를 저장하는 곳RequestContextHolder에는 여러타입이 있을 수 있어 ServletRequestAttribute로 다운캐스팅이 가능하다. (Spring에서 HTTP 요청/응답을 다루는 객체)

ServletRequestAttribute에 .getRequest()메서드를 사용할 경우 현재 요청정보를 가져올 수 있는 HttpServletRequest를 생성가능하다.

(테스트 환경에서는 MockMvc 또는 MockHttpServletRequest를 사용하여 테스트 작성)

 

변화과정은 다음과 같다.

RequestContextHolder -> ServletRequestAttribute -> HttpServletRequest

 

다른 방법으로는 HttpServletRequest 의존받는 방법도 있다.

@Aspect
@Component
@RequiredArgsConstructor
public class LoggingAspect {

    private final HttpServletRequest request;
    private final ObjectMapper objectMapper;

    @Around("@annotation(com.example.deliveryappproject.config.aop.annotation.LogTrace)")
//    @Around("bean(MenuCategoryMenuService)")
    public Object logAdminApiAccess(ProceedingJoinPoint joinPoint) throws Throwable {
        Long userId = (Long) request.getAttribute("userId");
        ...
    }
}

 

 

proceed() 이용하여 controller 앞뒤로 요청과 응답 로그를 출력하는 aop 작성해보았다.

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuthPermissionAspect {

    private final ObjectMapper objectMapper;

    @Around("@annotation(authPermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, AuthPermission authPermission) throws Throwable {

        // 요청 로그 작성
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        Long userId = (Long) request.getAttribute("userId");            // 사용자 id
        String traceId = UUID.randomUUID().toString().substring(0, 8);      // 로그 번호
        String url = request.getRequestURI();                               // 사용 url
        LocalDateTime requestTime = LocalDateTime.now();                    // 현재시간
        String method = request.getMethod();                                // 메서드 이름
        String requestBody = objectMapper.writeValueAsString(joinPoint.getArgs());      // 응답바디

        log.info(toRequestLog(traceId, method, userId, url, requestTime, requestBody));


        UserRole role = authPermission.role();
        AuthUser authUser = (AuthUser) joinPoint.getArgs()[0];

        if (!role.equals(authUser.getUserRole())) {
            throw new BadRequestException("접근 불가능한 권한 유저");
        }


        Object result = joinPoint.proceed();
        String responseBody = objectMapper.writeValueAsString(result);

        log.info(toResponseLog(traceId, method, url, userId, requestTime, result));

        return result;     // 이후 출력되는 메서드에서 예외를 던질 수 있음(Throwable)
    }


    private String toRequestLog(
            String traceId,
            String method,
            Long userId,
            String requestUrl,
            LocalDateTime requestTime,
            String requestBody
    ) {
        return String.format(
                "%n========== HTTP REQUEST LOG ==========%n" +
                        "Trace ID        : %s%n" +
                        "HTTP Method     : %s%n" +
                        "Request URI     : %s%n" +
                        "Request User ID : %s%n" +
                        "Request Time    : %s%n" +
                        "Request Body    : %s%n" +
                        "======================================",
                traceId, method, requestUrl, userId, requestTime, requestBody
        );
    }

    private String toResponseLog(
            String traceId,
            String method,
            String requestUrl,
            Long userId,
            LocalDateTime requestTime,
            Object result
    ) {
        return String.format(
                "%n========== HTTP RESPONSE LOG ==========%n" +
                        "Trace ID        : %s%n" +
                        "HTTP Method     : %s%n" +
                        "Request URI     : %s%n" +
                        "Request User ID : %s%n" +
                        "Request Time    : %s%n" +
                        "Response Body   : %s%n" +
                        "======================================",
                traceId, method, requestUrl, userId, requestTime, result
        );
    }
}

로깅 aop의 작성으로 다음과 같이 각 요청과 응답에 대한 로그를 출력할 수 있다.

 

 

 

 

 참고한 레퍼런스

https://youtu.be/7BNS6wtcbY8

 

https://engkimbs.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81AOP

 

[Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP

| 스프링 AOP ( Aspect Oriented Programming ) AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다. 관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로

engkimbs.tistory.com

 

https://adjh54.tistory.com/133

 

[Java] Spring Boot AOP(Aspect-Oriented Programming) 이해하고 설정하기

해당 글에서는 Spring AOP에 대해 이해하고 환경설정을 해보는 방법에 대해서 공유를 목적으로 작성한 글입니다.1) Spring AOP(Aspect-Oriented Programming, AOP)1. AOP 용어 이해하기 💡 Spring AOP란?- Spring AOP는

adjh54.tistory.com