관점 지향 프로그래밍
공통 관심 사항과 핵심 관심 사항을 분리하는 것
공통 관심 사항(로깅, 트랜잭션 관리, 예외처리)
여러 모듈이나 클래스에서 반복적으로 나타나는 기능
주요 비즈니스 로직과 독립적으로 전체 시스템에 필수적인 기능들을 제공한다.
핵심 관심 사항(장바구니 기능, 주문 처리)
애플리케이션의 주요 비즈니스 로직을 구성하는 기능
AOP에선 Aspect라는 개념을 사용하여 공통 관심 사항을 정의하고, Pointcut을 통해 애플리케이션의 어느 부분에 Aspect를 적용할지를 결정한다.
코드의 불필요한 중복을 줄여 유지보수와 관리 효율을 높이고, 본연의 비즈니스 로직에 집중할 수 있게 해주기 때문이다.
위와 같이 Member를 추가하고 가져오는 Member의 핵심 기능(관심사)가 있는데 만약 이 메소드들의 실행 시간을 코드로 측정해달라는 요구가 발생했다고 해보자.
그렇다면 메소드마다 위와 같은 비즈니스 로직 앞뒤로 시간을 측정하는 코드를 작성해야 한다.
지금은 메소드나 기능이 몇개 없지만 대규모의 서비스라면? 메소드에 해당 코드를 작성하는데만 상당한 시간이 소요될 것이다.
게다가 만약 코드 작성중 변경사항이 발생하면 다시 처음부터 수정해야하는 큰 문제가 있다.
이런 경우 사용하는 것이 AOP기술이며 Spring은 이어서 설명할 원리와 기능으로 마법같은 이 기술을 가능하게 한다.
AOP를 사용하지 않으면 반복적으로 사용되는 시간측정이나 로그기록같은 추가 기능들(공통 관심사)가 비즈니스 로직에 강하게 결합되어 관리가 어려워진다.
하지만 위와 같이 AOP를 적용하여 공통 관심사(기능)를 묶어서 따로 관리하고 특정 조건을 만족할 때 적용하면 변동이나 추가가 발생하더라도 적은 비용으로 대처할 수 있다.
Target
부가기능을 부여할 대상을 가리키는 말(공통 관심 사항이 적용될 실제 객체)
//타겟은 실제 부가기능이 부여될 대상
//(비즈니스 로직이있는 객체)
public class MyService {
public void doSomething() {
// 비즈니스 로직
}
}
advice
Target에게 제공할 부가기능을 담은 모듈로, 기능에 대한 코드와 시점이 명시되어있다.
무슨 기능을 어느 시점에?
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
Pointcut
advice를 적용할 JoinPoint를 고르는 작업
어떤 메소드에?
//포인트컷 안에 있는 경로에 해당하는 자바 파일들에 아래의 메소드를 적용(추가)시키겠다.
@Pointcut("execution(* org.example.day0704.test..*.*(..))")
private void cut(){
System.out.println("cut");
}
JoinPoint
프로그램 실행 중 advice가 적용될 수 있는 특정 위치(시점).
메소드 내에서 advice코드 적용 시점을 의미
- 메소드 호출, 실행
- 생성자 호출, 실행
- 필드 접근
어느 시점에?
// 메서드 실행 전에 조언을 적용(시점 지정)
@Before("serviceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
Aspect
해당 클래스가 advice(공통 관심 사항)를 담고있는 모듈임을 명시
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
}
advisor
PointCut과 advice를 하나씩 가지고 있는 Object
@Aspect을 사용해 advice메소드를 포함하고 있는 클래스를 의미한다.
Before
메소드 실행 전에 advice를 적용
After
메소드 실행 후에 advice를 적용
AfterReturning
메소드가 성공적으로 실행된 후에 advice를 적용
AfterThrowing
메소드가 예외를 던진 후에 advice를 적용
Weaving
AOP의 핵심 개념으로, Aspect를 핵심 기능 코드에 적용하는 과정이다.(AOP가 적용되는 원리)
- 컴파일 타임: 컴파일 시점에서 바이트 코드 수준애서 주입
- 로드 타임: JVM에 클래스를 로드할 때 클래스 로더가 주입
- 런타임: 동적으로 메소드 호출을 가로채서 Aspect를 적용한 프록시 객체를 생성(스프링은 이 방식)
ControllerAdvice도 비즈니스 로직에 붙어서 처리해야 했던 예외들을 한 곳으로 모아서 관리하고 처리하는데, 이것도 AOP인가?
AOP의 목표는 결과적으로 아래의 형태처럼 특정 부가기능을 수행하는 코드를 원하는 지점의, 원하는 시점에서 실행시키는 것이다.
하지만 이렇게 실행시키기 위해서는 부가기능에 대한 코드가 직접 메소드마다 작성 되어야 하는데, AOP는 모든 메소드에 코드를 작성하지 않고 한 번의 기능작성으로 마치 코드를 탈부착 하듯이 원하는 위치에서 실행시킨다.
어떤 원리로 이렇게 자동으로 동작할 수 있는걸까?
정답은 프록시 객체를 활용하는 것에 있다.
위의 자료에서 create기능의 비즈니스 로직을 호출하기 위한 방법은 service.create()이다. 그렇다면 부가 기능에 대한 실행코드가 끝나고 해당 메소드를 호출시키면 되지 않을까?
이를 시각적으로 표현하면 아래와 같다.
다른 객체의 다른 메소드에서 부가기능을 작성하여 실행하다가 원하는 시점에 진짜 실행하고자 하는 비즈니스 로직을 호출하는 형태로 만들면 비즈니스 로직에 직접적인 코드 작성이 필요 없어진다.
Spring AOP는 이러한 부가기능을 따로 작성해두고 위와같이 원하는 대상의 프록시(가짜)객체를 실제 객체 대신 Bean으로 등록 및 주입한다.
즉, 원래 객체 대신 주입되어 실제 메소드의 호출을 가로채서 AOP에 정의된 부가기능을 포함시킨 자신의 메소드를 실행시키는 방식으로 동작하는 것이다.
객체를 Bean으로 등록할 때, AOP에 정의된 기능을 적용해야하는 객체라면 위와 같은 형태의 프록시 객체를 만들어 실제 객체대신 등록하기 때문에 해당 객체의 메소드를 호출하면 실제 의존성 주입된 프록시 객체의 메소드를 호출하게 된다.
AOP 적용 대상이 아니라면 실제 객체를 Bean으로 등록하여 주입
AOP 적용 대상이라면, 프록시 객체를 Bean으로 등록하여 대신 주입(부가기능+비즈니스로직)
테코톡 콩하나님의 발표 자료가 상당히 잘되어 있어 해당 영상자료 사용
[10분 테코톡] 콩하나의 스프링 AOP