AOP란?
- Aspect Oriented Programming, 관점 지향형 프로그래밍
- 여기서의 관점이란, 프로그램을 여러 가지 관심사(Concern)로 나눠서 바라보는 것.
- 스프링으로 웹 페이지를 만들 때, 핵심 로직 관점(비즈니스 로직)과 부가기능 관점(로깅, 보안, 트랜잭션 등)을 나눠 생각한다는 뜻.
- 즉, 핵심 로직을 담당하는 패키지 / 클래스를 중심으로 코드를 짜고, 부가기능을 담당하는 클래스는 한 곳에 묶어버린다는 뜻.
나누면 뭐가 좋아?
- 관심사 분리: 핵심 로직과 부가기능을 분리해 코드의 가독성과 유지보수성을 높여준다.
- 재사용성 향상: 여러 클래스에서 공통으로 사용하는 기능을 한 곳에 정의해 재사용 가능.
- 유연성 제공: 특정 지점에서 로직을 쉽게 확장할 수 있음.
코드로 만들기
어떻게 부가기능 클래스를 만들고, 묶고, 핵심기능 담당 클래스에 적용시키지?
AOP 적용 구조
보통은 Aspect라는 패키지를 만들고, 그 안에 클래스를 만드는 경우가 대부분.
또한 부가기능을 담당하는 클래스를 aspect라고 함.
bash
com.example
│
├── aspect // AOP 애스펙트를 모아두는 패키지
│ ├── LoggingAspect.java // 로그 기록을 담당하는 애스펙트
│ ├── SecurityAspect.java // 보안 검사를 담당하는 애스펙트
│ └── TransactionAspect.java // 트랜잭션 관리를 담당하는 애스펙트
│
├── service // 비즈니스 로직(서비스) 클래스
│ ├── LoginService.java
│ └── UserService.java
│
├── controller // 컨트롤러 클래스(API 엔드포인트)
│ └── LoginController.java
│
├── config // 스프링 설정 클래스
│ └── AppConfig.java
│
└── AopApplication.java // 메인 애플리케이션 클래스
주로 쓰는 패키지
- 스프링 AOP: 프록시(Proxy) 기반으로 동작하며, 런타임에 애스펙트를 적용.
- AspectJ: 더 강력한 기능을 제공하며, 컴파일 타임과 로드 타임 위빙을 지원.
간단한 AOP는 스프링 AOP로 충분하지만, 더 정밀한 제어 필요 시 AspectJ를 고려하면 좋음!
Aspect 어노테이션
Aspect = 부가기능용 클래스를 만들려면 무엇이 필요할까?
일단, 클래스 위에 어노테이션으로 Aspect를 붙여야 한다.
java
@Aspect // 이 클래스는 AOP 애스펙트임을 선언
@Component // 스프링이 빈으로 등록하도록 설정
public class LoggingAspect {
// LoginService의 login() 메서드에 적용할 포인트컷 정의
@Pointcut("execution(* com.example.service.LoginService.login(..))")
public void loginPointcut() {}
// 메서드 실행 전에 로그 남기기
@Before("loginPointcut()")
public void logBefore() {
System.out.println("로그인 시도 중...");
}
// 메서드가 정상적으로 실행된 후 로그 남기기
@AfterReturning(pointcut = "loginPointcut()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("로그인 결과: " + result);
}
}
구성 요소
Aspect = 부가기능 클래스의 구성요소는 3가지가 존재한다.
- 포인트컷(Pointcut) : 부가기능(애스펙트)을 적용할 타겟 지점을 결정하는 규칙.
- 어드바이스(Advice) : 언제 부가기능을 실행할지 정의. (메서드 실행 전/후 등)
- 타겟(Target) : 애스펙트가 적용되는 핵심 기능을 담은 클래스나 메서드.
✨ 포인트컷(Pointcut)
부가기능(Aspect)을 어디에 적용할지를 정하는 규칙.
즉, “어떤 클래스의 어떤 메서드에 부가기능을 끼워 넣을까?”를 결정하는 역할을 함.
주로 메서드의 이름, 파라미터, 어노테이션, 클래스 위치 등을 기준으로 적용할 메서드를 선택.
java
@Pointcut("execution(* com.example.service.LoginService.login(..))")
public void loginPointcut() {}
- execution 표현식은 특정 클래스의 특정 메서드를 대상으로 부가기능을 적용하도록 함
- 위 예시에서는 LoginService 클래스의 login() 메서드에 부가기능을 적용하도록 지정.
- 이때 LoginService 클래스의 login() 메서드가 바로 타겟.
✨ 어드바이스(Advice)
어드바이스는 언제 부가기능을 실행할지를 정의.
- 메서드 실행 전, 후, 성공 후, 예외 발생 시 등 다양한 시점에서 부가기능을 실행할 수 있어요.
- 어드바이스 = 실제로 실행되는 부가기능의 로직.
📌 어드바이스 종류와 예시
@Before
- 메서드가 실행되기 전 부가기능 실행
java
@Before("loginPointcut()")
public void logBefore() {
System.out.println("로그인 시도 중...");
}
@AfterReturning
- 메서드가 성공적으로 끝난 후 실행
java
@AfterReturning(pointcut = "loginPointcut()", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("로그인 결과: " + result);
}
@AfterThrowing
- 메서드에서 예외가 발생한 후 실행
java
@AfterThrowing(pointcut = "loginPointcut()", throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("예외 발생: " + exception.getMessage());
}
@Around
- 메서드 전후 모두 부가기능 실행 (가장 강력한 어드바이스)
java
@Around("loginPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("메서드 실행 전");
Object result = joinPoint.proceed(); // 실제 메서드 실행
System.out.println("메서드 실행 후");
return result;
}