수업 복습

Spring AOP를 어노테이션 (@)으로 전환하는 흐름 정리

_김영인 2026. 1. 27. 14:33

 

 

오늘 수업의 핵심은 “XML로 하던 AOP 설정을 어노테이션 기반 (@Aspect, @Before …) 으로 옮기면 현업 코드가 어떻게 보이는지”이다.


 

1) 전제: Auto Proxy 설정

 

스프링 AOP는 실제로는 프록시 (Proxy) 로 동작한다.
스프링이 빈을 만들 때 “감싸는 대리 객체 (프록시)”를 하나 더 만들어서 메서드 호출 전 / 후에 끼어들 수 있게 한다.

그래서 어노테이션 AOP를 쓰려면 AspectJ Auto Proxy 를 켜야 한다.

 

(XML 설정) <aop:aspectj-autoproxy/>

 
<aop:aspectj-autoproxy />

 


 

2) <bean>을 줄이고 @Component 계열로 빈 등록하기

 

XML AOP에서 흔히 하던 방식이:

  • <bean id="logAdvice" class="...LogAdvice"/>
  • <aop:config> ... </aop:config>

였다면 어노테이션 AOP에서는 보통:

  • Advice 클래스에 @Aspect + 빈 등록 어노테이션 (@Component/@Service)
  • 포인트컷 / 어드바이스를 코드에 선언

으로 바뀐다.

 

@Component 계열 3종

  • @Controller : 웹 계층 (요청 / 응답)
  • @Service : 서비스 계층 (비즈니스 조합 / 트랜잭션 단위)
  • @Repository : DAO 계층 (DB 접근, 예외 변환)

중요 포인트
Advice는 “서비스 로직”이라기보다 공통 관심사 모듈이므로 보통 @Component를 더 많이 쓴다.
다만 @Service로 등록해도 “빈 등록”이라는 목적은 동일해서 동작 자체는 문제 없다.


 

3) 포인트컷 (Pointcut)을 @Pointcut으로 정의하기

 

포인트컷은 “어디에 적용할지 (대상 메서드 범위)”를 고르는 규칙이다.

업로드된 코드 기준:

PointcutCommon.java

package com.example.biz.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class PointcutCommon {
    @Pointcut("execution(* com.example.biz..*Impl.*(..))")
    public void aPointcut() {}

    @Pointcut("execution(* com.example.biz..*Impl.get*(..))")
    public void bPointcut() {}
}
  • aPointcut() : com.example.biz 하위의 *Impl 클래스 모든 메서드
  • bPointcut() : 그중에서도 get*()로 시작하는 메서드만

핵심 체크

  • “포인트컷이 여러 Advice에 중복되면” → 공통 포인트컷 클래스로 분리하는 게 정석이다.

보완점

PointcutCommon에는 @Aspect만 있고 빈 등록 어노테이션이 없다.

스프링은 보통 “스프링 빈으로 등록된 @Aspect”만 AOP 대상으로 인식한다.
따라서 아래처럼 @Component (또는 @Service)를 붙여서 스캔되게 만드는 게 안전하다.

 
@Aspect
@Component
public class PointcutCommon { ... }

 

4) 애스팩트 (Aspect) 결합: “언제 실행할지”를 @Before/@After…로 결정

 

AOP는 결국 2개를 결합한다.

  • Pointcut (어디에)
  • Advice (언제 / 무엇을)

그리고 이 둘의 결합 덩어리를 Aspect 라고 부른다.

 


 

6) “동작 시점별” 정리

 

아래는 업로드된 파일들을 기준으로 “각 Advice가 뭘 하는지”를 흐름대로 정리한 것이다.

 

(1) @Before : 실행 전에 찍는 로그 (가장 가벼운 관문)

 

LogAdvice.java

 
@Aspect
@Service
public class LogAdvice {
    @Before("PointcutCommon.aPointcut()")
    public void printLog() {
        System.out.println("작은티모님의 로그");
    }
}
  • aPointcut()에 걸린 모든 메서드 실행 전에 무조건 로그 출력
  • 파라미터 / 메서드명 정보까지 뽑고 싶으면 JoinPoint를 받으면 된다 (아래 PlusLogAdvice처럼)

포인트

  • 인증, 권한 체크, “호출됐는지” 추적 같은 가벼운 전처리에 자주 쓰인다.

 

(2) @Before + JoinPoint : “어떤 메서드 / 인자였는지”까지 뽑는 향상 로그

 

PlusLogAdvice.java

@Aspect
@Service
public class PlusLogAdvice {

    @Before("PointcutCommon.bPointcut()")
    public void printLog(JoinPoint jp) {
        System.out.println("향상된 로그 시작");

        String methodName = jp.getSignature().getName();
        System.out.println("핵심관심 메서드명 : " + methodName);

        Object[] args = jp.getArgs();
        System.out.println("메서드 인자들을 받아올수있음");
        for(Object arg : args) {
            System.out.println(arg);
        }

        System.out.println("향상된 로그 끝");
    }
}
  • JoinPoint가 바인드 변수 역할을 한다.
  • 핵심 메서드명 / 인자 목록을 받아와서 “호출 컨텍스트”를 만들 수 있다.

포인트

  • 이 형태가 현업에서 “요청 추적 로그 (Tracing)”, “디버깅 로그”, “감사 로그 (Audit)”의 출발점이다.
  • 단, 실서비스에서는 System.out 대신 로거 (SLF4J)로 그리고 민감정보 (비밀번호 / 토큰) 마스킹이 필수이다.

 

(3) @AfterReturning : 정상 반환 시 “반환값 (returning) 바인딩”

 

수업에서 말한:

  • returning="returnObj" 를 어노테이션으로 옮긴 게 이 부분이다.

AfterReturningAdvice.java 

@Aspect
@Service
public class AfterReturningAdvice {

    @AfterReturning(pointcut="PointcutCommon.bPointcut()", returning="returnObj")
    public void printLog(JoinPoint jp, Object returnObj) {
        System.out.println("+++티+++");
        ...
    }
}

 

여기서 핵심 규칙은 딱 1개이다.

returning 속성에 적은 이름 ("returnObj")메서드 파라미터 변수명 (Object returnObj)정확히 일치해야 한다.

 
@AfterReturning(..., returning="returnObj") public void printLog(..., Object returnObj)

이 매칭이 어긋나면 런타임에 바인딩이 안 된다.

 

또한 업로드된 코드에서 BoardDTO, MemberDTO로 분기하는 형태가 보이는데 (일부가 생략되어 있음),
이 패턴은 “반환 타입별로 로그를 다르게 찍고 싶다”는 의도이며 실무에서도 종종 쓴다.

 

  • 반환값 전체를 무조건 출력하면 로그가 과해질 수 있다.
    보통은 PK / 핵심 필드만 요약해서 남긴다.

 

(4) @AfterThrowing : 예외 발생 시 “예외 객체 (throwing) 바인딩”

 

AfterThrowingAdvice.java

@Aspect
@Service
public class AfterThrowingAdvice {

    @AfterThrowing(pointcut="PointcutCommon.aPointcut()", throwing="exceptObj")
    public void printLog(JoinPoint jp, Exception exceptObj) {
        System.out.println("예외발생시 출력되는 로그");
        System.out.println(exceptObj.getMessage());

        if(exceptObj instanceof NullPointerException) {
            System.out.println("ㅇㅇ월 ㅇㅇ일 확인된 예외");
            System.out.println("ㅇㅇ님이 조치함");
        }
        else {
            System.out.println("미확인 예외 발생!!!");
        }
    }
}
 

여기도 규칙이 동일하다.

 

throwing="exceptObj"
파라미터 Exception exceptObj

변수명이 반드시 일치해야 한다.

 

포인트

  • “확인된 예외 / 미확인 예외”로 갈라서 대응 흔적을 남기는 방식은 실무적인 운영 감각이 있다.
  • 다만 보통
    • 예외 타입
    • 요청 식별자 (requestId)
    • 사용자 식별자 (userId)
    • 핵심 파라미터 요약

를 함께 남겨서 “재현 가능한 로그”로 만든다.

 

(5) @Around : 실행 전 / 후를 감싸며 성능 측정 / 트랜잭션 경계까지 가능

 

AroundAdvice.java

@Service
public class AroundAdvice {

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        System.out.println("현재 수행중인 비즈니스 메서드 : " + methodName);

        StopWatch sw = new StopWatch();
        sw.start();
        Object returnObj = pjp.proceed();
        sw.stop();

        System.out.println("수행에 걸린시간 : " + sw.getTotalTimeMillis() + "ms");
        return returnObj;
    }
}

 

이 코드는 “성능 측정 (실행 시간)”이라는 목적 자체는 매우 좋다.
다만 어노테이션 AOP 방식으로 완성하려면 현재 파일에 2개가 빠져 있다.

보완점

  • 클래스에 @Aspect가 필요
  • 메서드에 @Around ("포인트컷")가 필요

어노테이션 AOP로 쓰려면 보통 아래 형태가 된다.

@Aspect
@Component
public class AroundAdvice {

    @Around("PointcutCommon.aPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        ...
    }
}

 

포인트

  • @Around는 “전 / 후 모두 개입” 가능해서 가장 강력하다.
  • 대표적으로
    • 성능 측정 (StopWatch)
    • 재시도 (Retry)
    • 서킷 브레이커 유사 패턴
    • 트랜잭션 / 락 / 캐시

같은 운영성 기능의 기반이 된다.


 

[AOP - @] 어노테이션 전환 흐름

  1. AOP 사용 선언 (프록시 설정)
    • @EnableAspectJAutoProxy 또는 <aop:aspectj-autoproxy/>
  2. Advice / Aspect 클래스들을 스프링 빈으로 등록
    • <bean> 대신 @Component/@Service 등으로 등록
  3. 포인트컷 정의
    • @Pointcut("execution(...)")
    • 중복되면 PointcutCommon처럼 분리
  4. 결합 (Aspect) 및 실행 시점 정의
    • @Aspect + @Before/@AfterReturning/@AfterThrowing/@Around
    • 반환 / 예외 바인딩은 returning="변수명", throwing="변수명"과 파라미터명이 정확히 일치해야 함

 

  • 핵심 코드 오염 방지: 비즈니스 메서드에 로그 / 성능 코드가 난립하지 않는다.
  • 일괄 적용 / 일괄 수정: 포인트컷 한 줄 바꾸면 적용 범위가 통째로 바뀐다.
  • 운영 관측성 (Observability) 강화: “언제 / 어디서 / 무슨 파라미터로 / 얼마나 걸렸는지”를 표준화할 수 있다.
  • 장애 대응 속도 향상: 예외 로깅 (@AfterThrowing)만 잘 설계해도 재현 / 원인분석 시간이 크게 줄어든다.
  • 성능 최적화 근거 확보: StopWatch 기반으로 병목 후보를 데이터로 뽑을 수 있다.

'수업 복습' 카테고리의 다른 글

JDBCTemplate + RowMapper  (0) 2026.01.28
스프링에서의 JDBC 정리  (0) 2026.01.28
바인드 변수로 핵심관심 정보 뽑아오기  (0) 2026.01.26
바인드 변수  (0) 2026.01.26
AOP 질의응답  (0) 2026.01.26