AOP란 무엇일까? - 01

AOP란 무엇일까?

AOP는 Aspect Oriented Programming의 약자로 ‘관점 지향 프로그래밍’
OOP는 Object Oriented Programming인 것이 생각날 것이다.
객체 지향과 관점 지향이라는 차이점이 존재하지만 이것이 반대 개념을 의미하는 것은 아니다.

객체 지향 프로그래밍에서 공통된 기능을 모든 모듈에 적용해야할 경우 상속과 같은 방법을 사용할 수 있지만 자바는 다중 상속이 불가능하다는 점과 공통 기능, 핵심 기능이 마구자비로 뒤섞여 비효율적인 소스가 될 수 있다.

AOP는 이러한 문제점을 해결 할 수 있다.

핵심 기능과 공통 기능을 분리 시키고 필요할 때만 사용할 수 있게 하는 것이다.

결국 OOP 구조를 돕는 보조적인 역할을 하는 셈이다.

실제 업무 중 AOP로 활용한다면 시간과 자원을 절약할 수 있는 경우가 많다.

업무 중 있었던 에피소드를 하나 소개해볼까 한다.

데이터를 이행하게 되었다.

위는 간단하 예시이지만 ASIS -> TOBE로의 데이터 이행 작업을 해야했다.

1
2
3
4
5
6
7
8
9
/**
* 게시글 이행 Class
*/
public class TrnsBoard implements TrnsBase {
@Override
public void startContent() {
...
}
}
1
2
3
4
5
6
7
8
9
/**
* 방문자 기록 이행 Class
*/
public class TrnsVisit implements TrnsBase {
@Override
public void startContent() {
...
}
}

이와 같은 게시글, 방문자 기록 이행 클래스가 존재한다고 가정해보자.
이때 실제 이행 이전에 예상 시간 및 정확한 이행을 위해 시간 측정은 필수이다.
(이행 테스트 시나리오에서의 측정 시간과 차이가 난다면 무언가 문제가 있음을 알 수 있다.)

하지만 해당 코드는 큰 문제점을 가지고 있다.
그것은 바로 중복되는 소스로 인해 유연한 로직을 구현할 수 없다는 점이다.

만약 시간 측정 방식을 바꾸거나 해당 로그 표기 방법을 바꿔야한다면 프로젝트 내 해당 소스가 반영된 모든 메소드를 수정해야한다.

바로 관심사의 분리에 실패한 코드인 것이다.

이러한 구조를 조금 개선해볼 수 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 프록시 객체
*/
public class TrnsProxy implements TrnsBase {
private TrnsBase target;

/**
* 생성자 초기화 클래스
*/
public TrnsProxy(TrnsBase target) {
this.target = target;
}

@Override
public startContent() {
long sTime = System.currentTimeMillis(); // 실행 전 시간 측정
target.startContent(); // 이행(핵심 기능 수행)
long eTime = System.currentTimeMillis(); // 실행 후 시간 측정
System.out.println("수행 시간 : " + (eTime - sTime)); // 실행 시간 측정
}
}

또 다른 클래스를 생성하여 게시글, 방문자 이행와 같이 ‘TrnsBase’라는 인터페이스를 상속받아 구현한다.
다른 점이라면 생성자를 통해 또 다른 구현 클래스를 전달 받고 실행시간을 출력하는 동작과 핵심 로직을 동시에 수행한다는 점이다.

이로써 핵심 기능은 다른 객체에서 수행하고 공통 기능만을 제공하게 되었다.

  • 실행 전 시간 측정
  • 핵심 기능 수행
  • 실행 후 시간 측정, 실행 시간 출력

이러한 실행 구조를 가지게 된다.
클라이언트와 타겟 사이에 대행을 끼워 넣는 것이다.
이러한 객체를 프록시 객체라 부르며 AOP의 핵심인 공통 기능과 핵심 기능을 분리할 수 있다.

이렇게 핵심 기능과 공통 기능을 분리하여 삽입하는 방법은 크게 3가지가 존재한다.

  • 컴파일 전에 별도 컴파일러를 통해 핵심 기능 사이에 공통 기능을 삽입하여 최종 바이너리를 만드는 방식(AspectJ)
  • 클래스 로딩 시, 별도의 에이전트를 이용하여 JVM이 클래스를 로딩하는 시점에 해당 클래스의 바이너리를 수정하여 삽입하는 방식(AspctWerkz)
  • 런타임 시, 프록시 객체를 생성하여 핵심 로직을 구현한 객체에 접근하는 방식(Spring AOP)

각 방법별로 장단점이 존재하겠지만 간단히 살펴본다면 컴파일 시 타겟 파일을 수정하거나 클래스 로딩 시 바이너리를 가로채 조작하는 방법은 프록시 객체를 별도로 생성할 필요가 없으며 적용할 수 있는 시점이 다양하므로 자유롭다는 장점을 가지고 있다.(프록시 방식은 객체가 생성된 이후)
하지만 바이너리 코드를 변경하기 위해선 클래스를 로드하는 JVM에 옵션 설정이 필요하며 별도의 에이전트가 필요하다는 단점이 존재한다.

스프링에서는 프록시를 이용한 AOP방식을 지원한다.

대부분의 기능은 프록시 방식으로 충분히 구현이 가능하며 이보다 더 깊은 기능을 필요로 할때 바이너리를 조작하는 방법을 사용하는 것이 좋다.

Share