수업 복습

바인드 변수

_김영인 2026. 1. 26. 17:33

 

같은 로그도 메서드 / 인자 / 반환 / 예외에 따라 출력이 달라지는 이유

 

오늘 수업의 핵심은 Spring AOP에서 바인드 변수 (binding) 를 활용해 핵심관심사 (비즈니스 메서드)의 정보를 개발자가 직접 넘기지 않아도 자동으로 받아오는 구조였다.

 

로그를 찍는 행위는 같아도

  • 어떤 메서드가 호출되었는지
  • 어떤 인자가 들어왔는지
  • 반환값이 무엇인지
  • 예외가 무엇인지

에 따라 출력이 달라지는 “동적 로그”를 만들 수 있다.


 

0) 바인드 변수 (binding)란?

 

바인드 변수는 Spring AOP가 Advice 메서드 파라미터에 런타임 정보를 자동 주입 (binding)하는 방식을 말한다.

특히 XML 설정에서 다음처럼 이름을 지정해 매핑하는 값이 대표적이다.

  • returning="returnObj" : 정상 반환 값을 returnObj로 바인딩
  • throwing="exceptObj" : 발생한 예외 객체를 exceptObj로 바인딩

정리하면 구조는 이렇게 나뉜다.

  • JoinPoint / ProceedingJoinPoint
    → Advice 파라미터로 선언하는 순간 Spring이 자동 주입해주는 “컨텍스트 객체”
  • returnObj / exceptObj
    → XML에서 returning/throwing으로 “이름까지 지정해” 주입되는 “바인딩 변수”

중요한 포인트는 하나다.

 

파라미터 타입과 선언 형태 (특히 returning/throwing 이름 매칭)가 맞아야 Spring이 자동으로 꽂아준다.


 

1) JoinPoint로 메서드명 / 인자 뽑기 (PlusLogAdvice)

 

가장 기본적인 패턴은 JoinPoint를 받아 메서드명과 인자 목록을 출력하는 것이다.

  • getSignature()는 문자열이 아니라 Signature 객체를 반환한다.
  • jp.getSignature().getName()을 하면 핵심 메서드명을 얻는다.
  • jp.getArgs()를 하면 호출 시 전달된 인자 배열 (Object[])을 얻는다.
 
public class PlusLogAdvice {
  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("향상된 로그 끝");
  }
}

 

이 구조가 중요한 이유는 “동적 바인딩” 때문이다.
같은 Advice라도 어떤 메서드가 호출되었는지 어떤 값이 인자로 들어왔는지에 따라 출력이 달라진다.

 

보통 이 형태를 기반으로

  • 공통 로깅
  • 감사 로그
  • 파라미터 검증 로깅

등을 구현한다.

 

다만 System.out.println 대신 SLF4J/Logback을 쓰고 인자에는 개인정보가 섞일 수 있어 마스킹이 필요하다.


 

2) ProceedingJoinPoint로 성능 측정 + 수행 / 반환 강제 (AroundAdvice)

 

Around Advice는 “전 / 후를 감싸는” 형태라 가장 강력하다.
이때 ProceedingJoinPoint가 등장한다.

  • ProceedingJoinPoint는 JoinPoint를 확장한 형태 (더 많은 기능 포함)
  • Around Advice에서는 pjp.proceed() 호출이 핵심
    • 이걸 호출하지 않으면 비즈니스 메서드가 실행되지 않는다
  • 반환값을 받아서 그대로 return 해야 호출 흐름이 깨지지 않는다.

그리고 이 구조가 성능 측정에 특히 적합하다.

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;
  }
}
 

실무에서는 Around로

  • 성능 측정 (APM)
  • 트랜잭션 경계
  • 공통 전 / 후 처리

를 많이 구현한다.

 

단, 전 메서드에 다 걸면 로그가 폭발할 수 있어 Pointcut을 좁히는 설계가 중요하다.

 


 

3) AfterReturning (returnObj)으로 반환 타입 기반 처리 (AfterReturningAdvice)

 

AfterReturning은 “정상 반환”에만 실행된다.
여기서 핵심은 returnObj 바인딩이다.

  • returning="returnObj"로 설정하면
  • Advice 파라미터의 Object returnObj로 반환값이 들어온다

이걸 이용하면 반환 타입에 따라 후처리 로깅 / 권한 체크가 가능해진다.

public class AfterReturningAdvice {
  public void printLog(JoinPoint jp, Object returnObj) {
    System.out.println("+++티+++");

    if(returnObj instanceof BoardDTO) {
      System.out.println("게시글 관련 메서드");
    }
    else if(returnObj instanceof MemberDTO) {
      MemberDTO member = (MemberDTO)returnObj;
      if(member.getMrole().equals("ADMIN")) {
        System.out.println(">>> 관리자 로그인 <<<");
      }
      else {
        System.out.println(">>> 일반 로그인 <<<");
      }
    }
    else {
      System.out.println("SELECTALL 관련 메서드");
    }

    System.out.println("+++모+++");
  }
}

 

여기서 instanceof + 다운캐스팅은 세트로 다닌다.
반환값이 Object로 들어오기 때문에 “누구인지 판별” 후에 안전하게 캐스팅해야 한다.

 

추가로 자주 실수하는 포인트가 있다.

 

“selectAll이 리스트 반환이라서 Advice가 안 걸린다”처럼 보일 수 있는데 실제로는 Pointcut 조건 (예: get*만 적용) 때문에 애초에 제외되는 경우가 많다.
동작 여부는 “반환 타입”보다 “포인트컷이 어떤 메서드명을 잡는지”가 더 큰 변수다.


 

4) AfterThrowing (exceptObj)으로 예외 확인 / 분류 (AfterThrowingAdvice)

 

AfterThrowing은 “예외가 발생했을 때만” 실행된다.
여기서도 바인딩이 핵심이다.

  • throwing="exceptObj"로 설정하면
  • Advice 파라미터로 예외 객체가 들어온다
public class AfterThrowingAdvice {
  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("미확인 예외 발생!!!");
    }
  }
}

 

중요한 원칙은 “운영 로그”와 “사용자 메시지”를 분리하는 것이다.
개발 / 운영 단계에서는 상세 로그로 원인 추적을 쉽게 하되 사용자에게는 내부 정보가 노출되지 않게 처리하는 것이 실무 기본이다.


 

5) 오늘 핵심 4가지

 

바인딩을 활용하면 다음 4가지를 핵심 코드 수정 없이 일관되게 처리할 수 있다.

  • 핵심 메서드명 / 인자 파악: JoinPoint로 공통 로그
  • 성능 체크: ProceedingJoinPoint로 실행 시간 측정
  • 반환 타입 기반 분기: returnObj로 권한 / 유형 후처리
  • 예외 확인 / 분류: exceptObj로 예외 로깅 / 심각도 분류

 

6) XML 설정에서 “바인딩이 걸리는 지점”

 

포인트컷을 2개로 나누고 각 Advice를 역할에 맞게 걸면 흐름이 명확해진다.

  • aPointcut: Impl 전체 메서드 (범위 넓음)
  • bPointcut: get* 메서드만 (조회 계열만)

그리고 after-returning / after-throwing에서는 바인딩 이름이 반드시 필요하다.

<aop:after-returning method="printLog"
                     pointcut-ref="bPointcut"
                     returning="returnObj"/>

<aop:after-throwing method="printLog"
                    pointcut-ref="aPointcut"
                    throwing="exceptObj"/>

 

JoinPoint / ProceedingJoinPoint는 파라미터로 선언하면 자동 주입되지만
returning/throwing은 XML에서 이름을 지정해야 값이 바인딩된다는 점이 핵심이다.


 

 

공통 로그 / 성능 측정 / 예외 분류 / 반환 기반 권한 체크는 프로젝트가 커질수록 “반복 + 누락 + 불일치” 문제가 생긴다.
AOP 바인딩은 이를 핵심 코드에 손대지 않고 일관되게 적용할 수 있게 해준다.

 

다음 조합은 정말 자주 사용된다.

  • 서비스 메서드 실행 시간 측정 (Around)
  • 예외 발생 시 통합 로깅 (AfterThrowing)
  • 조회 결과 기반 후처리 (AfterReturning)
  • 호출 메서드 / 인자 기반 감사 로그 (Before + JoinPoint)