AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 공통적으로 사용되는 관심사(Concern)를 핵심 비즈니스 로직과 분리하여 모듈화하는 프로그래밍 기법입니다.
목적
예시 관심사
| 용어 | 설명 |
|---|---|
Target | 핵심 로직을 포함한 객체 (예: SampleServiceImpl) |
JoinPoint | AOP가 적용 가능한 지점 (예: 메서드 실행 전/후) |
Advice | 삽입되는 공통 관심사 로직 (예: 로그 출력) |
Pointcut | 어떤 JoinPoint에 Advice를 적용할지 지정 |
Aspect | Pointcut + Advice 묶음 (공통 기능 단위) |
Proxy | 클라이언트 대신 호출되는 래퍼 객체 |
Weaving | Advice를 실제 코드에 적용하는 과정 |
| 어노테이션 | 설명 |
|---|---|
@Before | JoinPoint 실행 전에 Advice 실행 |
@AfterReturning | 정상 종료된 경우 Advice 실행 |
@AfterThrowing | 예외 발생 시 Advice 실행 |
@After | 정상/예외 관계 없이 항상 실행 |
@Around | 메서드 실행 전/후 모두 제어 가능 (가장 강력함) |
💡 예제
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
log.info("메서드 실행 전 로깅");
}
| 표현식 | 의미 |
|---|---|
execution(...) | 메서드 시그니처 기반 |
within(...) | 클래스 기준 |
args(...) | 전달되는 파라미터 기준 |
@annotation | 특정 어노테이션 기준 |
this | 주어진 인터페이스를 구현한 객체를 대상으로 Pointcut 지정 |
💡 예제(Advice + Pointcut)
@Before("execution(* org.scoula.sample.service.SampleService*.*(..))")
public void logBefore() {
...
}
① 핵심 로직 (Target)
public class SampleServiceImpl implements SampleService {
public Integer doAdd(String str1, String str2) throws Exception {
return Integer.parseInt(str1) + Integer.parseInt(str2);
}
}
② 공통 로직 (Advice)
@Aspect
@Component
@Log4j2
public class LogAdvice {
@Before("execution(* org.scoula.sample.service.SampleService*.*(..))")
public void logBefore() {
log.info("====== 메서드 실행 전 ======");
}
@AfterThrowing(pointcut = "execution(* org.scoula.sample.service.SampleService*.*(..))", throwing = "exception")
public void logException(Exception exception) {
log.info("예외 발생!! " + exception);
}
@Around("execution(* org.scoula.sample.service.SampleService*.*(..))")
public Object logTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.info("소요 시간: " + (end - start));
return result;
}
}
③ 설정 클래스
@Configuration
@ComponentScan(basePackages = {
"org.scoula.advice",
"org.scoula.sample.service"
})
@EnableAspectJAutoProxy
public class RootConfig { }
④ 테스트 클래스
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { RootConfig.class })
@Log4j2
class SampleServiceTest {
@Autowired
private SampleService service;
@Test
public void doAdd() throws Exception {
log.info(service.doAdd("123", "456"));
}
@Test
public void addError() throws Exception {
log.info(service.doAdd("123", "ABC"));
}
}
💡 AOP 작동 흐름 간단 요약
Client → Proxy 객체 → Advice 실행 → Target 실행 → 결과 반환
@Before("execution(* org.scoula.sample.service.SampleService*.doAdd(String, String)) && args(str1, str2)")
public void logBeforeWithParam(String str1, String str2) {
log.info("str1: " + str1);
log.info("str2: " + str2);
}
📌 args()를 활용하면 메서드 인자를 Advice 메서드의 파라미터로 받을 수 있음
| 상황 | Advice 종류 | Pointcut 예시 |
|---|---|---|
| 모든 서비스 메서드 호출 전 로깅 | @Before | execution(* ..service..*.*(..)) |
| 예외 발생 시 예외 내용 기록 | @AfterThrowing | execution(* ..*.*(..)) |
| 메서드 실행 시간 측정 | @Around | execution(* ..*.*(..)) |
| 특정 메서드에만 적용 | @Before | @annotation(MyLog) |
공통 기능(관심사)를 한 곳에 모아 코드 중복 없이 관리할 수 있음
핵심 로직은 깔끔하게 유지, 공통 기능은 자동 삽입
Proxy 객체가 실제 객체 대신 동작하며 Advice를 실행
@Aspect, @Before, @Around, @AfterThrowing 등으로 유연하게 제어 가능
execution, args, @annotation 등으로 Pointcut을 정밀하게 지정 가능