오늘 수업의 핵심은 “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)이 정확히 일치해야 한다.
이 매칭이 어긋나면 런타임에 바인딩이 안 된다.
또한 업로드된 코드에서 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 - @] 어노테이션 전환 흐름
- AOP 사용 선언 (프록시 설정)
- @EnableAspectJAutoProxy 또는 <aop:aspectj-autoproxy/>
- Advice / Aspect 클래스들을 스프링 빈으로 등록
- <bean> 대신 @Component/@Service 등으로 등록
- 포인트컷 정의
- @Pointcut("execution(...)")
- 중복되면 PointcutCommon처럼 분리
- 결합 (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 |