과제를 진행하기에 앞서 복습겸 개념에 대해 설명하도록 하겠습니다. ㅎㅎ
일단, aop 관점지향 프로그래밍은 핵심 비즈니스 로직과 별개로 반복적으로 사용하는 부가기능 (로깅, 트랜잭션 관리, 보안 등)을 별도로 분리하여 코드의 중복을 최소하하는 방법입니다.
<핵심 개념>
Aspect(관점), Advice(보조 로직), JoinPoint(적용 지점), PointCut(적용 범위 지정)
@Aspect와 Advice 종류
@Before → 메서드 실행 전 AOP 실행
@After → 메서드 실행 후 AOP 실행
@Around → 메서드 실행 전, 후 모두 AOP 실행 가능
@AfterReturning → 메서드가 정상적으로 실행된 후 AOP 실행
@AfterThrowing → 예외 발생 시 AOP 실행
Spring AOP에서 Pointcut을 설정할 때 사용할 수 있는 표현식은 ?
@Pointcut("execution(* org.example.expert..*(..))") // 특정 패키지 이하 모든 메서드
@Pointcut("execution(* org.example.expert.domain.user.controller.UserController.getUser(..))") // 특정 메서드
@Pointcut("within(org.example.expert.domain.user.controller..*)") // 특정 패키지 내부 모든 클래스
@Pointcut("bean(*Service)") // 이름이 'Service'로 끝나는 모든 빈에 적용
여기서 더 나아가서 필요한 개념이 있다면 ?
쁘로오오옹ㄱ쒸위위?
이게 뭐야~~~ 라고 할 수 있지만, 간단히 생각하시면 됩니다. 나중엔 프록시 객체 쏼롸쏼라 나오면 그때 더 공부하시면 좋을 것 같습니다.!
private 메서드에는 적용 불가
final 클래스에는 적용 불가 (CGLIB 사용 시)
Spring AOP는 메서드 실행 시점(joinPoint)만 제어 가능 → AspectJ는 필드 접근, 객체 생성 시점 등도 제어 가능
현재 코드에는 큰 문제점이 있으며, 요구사항에 맞춰 수정해야합니다.
@After → @Before 로 변경하여 메서드 실행 전에 로그가 남도록 수정
UserAdminController의 changeUserRole()을 대상으로 AOP가 적용되도록 Pointcut 수정
package org.example.expert.aop;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AdminAccessLoggingAspect {
private final HttpServletRequest request;
@Before("execution(* org.example.expert.domain.user.controller.UserAdminController.changeUserRole(..))")
public void logBeforeChangeUserRole(JoinPoint joinPoint) {
String userId = String.valueOf(request.getAttribute("userId"));
String requestUrl = request.getRequestURI();
LocalDateTime requestTime = LocalDateTime.now();
log.info("Admin Access Log - User ID: {}, Request Time: {}, Request URL: {}, Method: {}",
userId, requestTime, requestUrl, joinPoint.getSignature().getName());
}
}
AOP는 Spring 내부에서 프록시를 통해 메서드 실행을 가로채고(advice 적용), 이후 원래 메서드를 실행하는 방식으로 동작한다.
이때 프록시가 생성되는 방식(JDK Dynamic Proxy vs CGLIB)과 AOP 적용 범위를 이해하면, 보다 안정적인 코드 설계가 가능하다.
📌 추가 학습
Spring AOP는 기본적으로 JDK Dynamic Proxy 를 사용하므로 인터페이스가 있는 클래스에만 AOP 적용 가능
인터페이스가 없을 경우 CGLIB 기반의 바이트코드 조작 방식으로 AOP 적용
Spring AOP: 런타임 기반, 프록시 방식 → 상대적으로 성능이 좋고 관리가 쉬움
AspectJ: 컴파일 타임 기반, 더 강력한 기능 제공 (필드 접근, 객체 생성 시점 등)
📌 이해가 필요한 부분
Spring AOP의 한계: 메서드 실행 단계에서만 Advice를 적용할 수 있음
AspectJ를 사용해야 하는 경우: 필드 접근 시 로깅, 객체 생성 시 인터셉트 등
현재 요구사항에서는 Spring AOP로 충분하지만, 더 복잡한 AOP 적용이 필요하다면 AspectJ까지 고려할 필요가 있다.
@Component, @Aspect 가 붙어 있지 않음
Spring AOP는 내부(private) 메서드에는 적용되지 않음
AOP 적용 대상이 final class 또는 private 메서드 일 경우 프록시 생성 불가
AOP 내부에서 this 호출 시 Advice가 적용되지 않는 문제
프록시 방식의 특성 때문에 같은 클래스 내의 메서드를 호출할 경우 AOP 적용되지 않음
해결 방법: ApplicationContext에서 해당 빈을 직접 주입받아 호출하면 AOP 적용 가능
@Component
public class MyService {
@Autowired
private ApplicationContext applicationContext;
public void outerMethod() {
applicationContext.getBean(MyService.class).innerMethod(); // AOP 적용됨
}
@Transactional
public void innerMethod() {
System.out.println("AOP 적용 메서드 실행");
}
}
AOP는 프록시 객체를 생성하고 이를 통해 메서드 호출을 가로채기 때문에 성능 오버헤드가 발생할 수 있다.
많은 트래픽을 처리하는 서비스라면 AOP의 성능 영향을 고려해야 한다.
Pointcut을 최소한으로 설정 (불필요한 클래스/메서드에 적용하지 않도록)
복잡한 로직을 AOP 내부에서 실행하지 않도록 주의 (@Around 사용 시 부하 주의)
AspectJ 대신 Spring AOP 사용 (AspectJ는 강력하지만 성능 오버헤드 있음)
AOP 적용 범위를 명확하게 지정하여 불필요한 Proxy 생성을 방지
Spring AOP 공식 문서 📖
🔗 https://docs.spring.io/spring-framework/reference/core/aop.html
🔗 https://jojoldu.tistory.com/139
🔗 https://woowabros.github.io/experience/2020/06/22/spring-aop.html
🔗 https://velog.io/@devjunu/Spring-AOP와-Proxy
AOP는 Spring 프록시 기반으로 동작 → this 키워드를 사용한 내부 메서드 호출은 AOP가 적용되지 않음
해결법: ApplicationContext에서 빈을 직접 가져와 호출
AOP와 트랜잭션이 함께 동작할 때 발생할 수 있는 문제
@Transactional 메서드를 AOP에서 감싸는 경우 트랜잭션이 예상과 다르게 커밋될 가능성 있음
해결법: AOP와 트랜잭션 적용 순서를 조정하거나, 필요한 경우 AspectJ를 고려
AspectJ는 AOP보다 더 강력한 기능 제공 (필드 접근, 객체 생성 감지 가능)
하지만 런타임 성능 오버헤드가 크므로, 일반적인 Spring AOP로 해결 가능한 경우가 많음
Spring Boot 3.x 이후, @Around를 사용할 때 Spring Proxy 방식이 조금 더 개선됨
Spring 6.x부터 AOP 내부에서 네이티브 이미지와의 호환성이 더 강화됨 (Native Spring 활용 가능)