같은 로그도 메서드 / 인자 / 반환 / 예외에 따라 출력이 달라지는 이유
오늘 수업의 핵심은 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)
'수업 복습' 카테고리의 다른 글
| Spring AOP를 어노테이션 (@)으로 전환하는 흐름 정리 (0) | 2026.01.27 |
|---|---|
| 바인드 변수로 핵심관심 정보 뽑아오기 (0) | 2026.01.26 |
| AOP 질의응답 (0) | 2026.01.26 |
| Spring XML 기반 AOP 실습 정리 (0) | 2026.01.23 |
| XML 기반 AOP 설정 실습 흐름 (0) | 2026.01.23 |