[Spring] AOP - 관점 지향 프로그래밍 이란?
Spring

[Spring] AOP - 관점 지향 프로그래밍 이란?

728x90

AOP는 Aspect Oriented Programming을 줄인말로 "관점지향" 프로그래밍이라는 뜻을 가지고 있습니다!

 

네?!?! 관점지향이요?? 객체지향(OOP)이랑 절차지향(PP)만 있는거 아니었어요?? 전 중립이어서 아무 관점도 없는데요..??

 

...라고 하시진 않겠죠 ㅋㅋㅋ.. 농담입니다. AOP는 스프링 철학의 기반이 된 POJO를 이루는 삼각형에도 등장한 아주 중요한 개념입니다. 간단해보이면서도 새로운 단어와 개념이 많아서 이해하는데는 조금 시간이 걸렸던 것 같습니다.

 

오늘은 이 AOP에 대해서 천천히 알아보겠습니다.

[*중요*] 그냥 AOP 라고만 한다면 "AOP"와 AOP의 구현체를 제공하는 "스프링 AOP"에 대해 중의적일 수 있으므로, 두개를 구분지어서 말씀드리는 점 꼭 참고해주세요!

AOP (Aspect Oriented Programming)

처음 이 단어를 보게되면 관점지향(AOP)은 객체지향(OOP)과 절차지향(PP)과 다른 완전히 다른 새로운 개념인가요? 라는 의문이 드실 수 있습니다. 결론은 아닙니다. AOP는 기본적으로 OOP환경에서만 의미있는 개념입니다. 따라서 AOP는 OOP를 보완해주는 프로그래밍 기법이라고 보시면 됩니다.

 

AOP를 정의해보자니 표준화된 한문장 정의를 찾을 수가 없었는데요 ㅠ 그래서 우선 제일 대표격인 기업 또는 웹사이트에서 나온 정의를 나열해 보겠습니다. 

  • spring.io 정의: AOP는 OOP를 보완하여 프로그램 구성을 색다른 방식으로 생각할 수 있게 해줍니다. OOP는 객체 단위로 모듈을 구성하지만, AOP는 관점(Aspect) 단위로 모듈을 구성합니다. 관점(Aspect)은 여러 타입과 객체에 걸쳐있는 관심사(crosscutting concern)를 모듈화 할 수 있게 해줍니다.
  • jboss 정의: 관점(Aspect)은 여러 메소드, 클래스 또는 객체에 흩어져있는 공통 기능을 말합니다. 이렇게 흩어져있는 기능들을 보고 crosscutting concern 이라고 하는데, AOP는 이것을 추상화하고 캡슐화하는 것을 지향합니다.
  • Baeldung 정의(제일 많이 쓰임): AOP는 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다.

후... 관점(aspect)..? 횡단(crosscutting)..? 관심사(concern)..? 혹시 벌써 포기하고싶으셨나요? 걱정하지마세요 천천히 하나씩 알아보겠습니다.

 

관심사(concern)

관점지향 프로그래밍인데 왜 관점보다 관심사를 먼저 설명하나요? 먼저 관심사에 대한 개념을 알아야 관점(Aspect)을 이해하기 수월하기 때문입니다.

 

아하 근데 잠깐만요 concern의 영어 뜻은 '우려', '걱정' 이런건데 왜 관심사라고 하나요?  concern이 "관심사"라는 프로그래밍 용어로써 쓰이기 시작한건 에츠거 다익스트라(Edsger Dijkstra - 다익스트라 알고리즘 그분 맞습니다)가 1974년 발표한 논문에서 최초로 등장한 용어인 관심사 분리(Separation of Concerns - SoC)에서부터 입니다.

 

컴퓨터 공학(Computer Science)에서 SoC란, 하나 프로그램을 여러개의 서로 간섭하지 않는 개별적인 부분으로 나누는 것에 대한 설계 디자인 개념입니다. 그리고 한 부분에 대한 단위를 concern 이라고 정의하죠. 따라서 하나의 concern 은 프로그램에 영향을 주는 코드의 집합을 뜻합니다. (좀 모호하지만 IBM에서도 설명하기 모호한 단어라고 언급을 하네요)

 

**어떻게보면 프로그래밍 용어 concern 은 한국어로 한단어로 표현하기 어려운 단어인것 같은데요. 누가 처음에 한국어로 번역했는지는 알 수 없지만, 관심의 사전적 정의가 "어떤 것에 마음이 끌려 주의를 기울임" 인걸로 봐서 "주의를 기울이는 대상"을 뜻하는 관심사로 표현한게 아닐까 하고 추측해봅니다..

 

횡단 관심사(cross-cutting concern)

관심사(concern)를 이해하셨으면 이 단어는 간단합니다. 직역하면 cross-cutting 은 가로지르는, 횡단이라는 뜻이죠. 어디를 가로지르냐?? 바로 무수히 많은 클래스 또는 객체이죠. 근데 왜 가로지른다는 표현을 쓸까요? 예제로 살펴봅시다.

 

cross-cutting concern의 가장 대표적인 예시는 바로 로그찍기(Logging) 입니다.

만약 모든 메소드에 "실행되는데 걸리는 시간"을 로그로 찍고싶다면 보통 어떻게 할까요? 저는 아직 모자란 응애 개발자여서 이렇게 할겁니다

@Service
public class ProfileService {
    public void createProfile() {
        long begin = System.currentTimeMillis();
        비즈니스로직();
        long end = System.currentTimeMillis();
        System.out.println("createProfile 걸린시간: " + (end - begin));
    }

    public void updateProfile() {
        long begin = System.currentTimeMillis();
        비즈니스로직();
        long end = System.currentTimeMillis();
        System.out.println("updateProfile 걸린시간: " + (end - begin));
    }

    public void deleteProfile() {
        long begin = System.currentTimeMillis();
        비즈니스로직();
        long end = System.currentTimeMillis();
        System.out.println("deleteProfile 걸린시간: " + (end - begin));
    }
}
직장상사: ㅇㅇ씨 디버깅해야되니까 프로젝트에 있는 모든 메소드 호출될 때마다 실행시간 찍히게 해주세요.
나: 앗 한 일주일정도 걸릴거같은데 괜찮으세요? 물론 다시 다 지우는데도 일주일 주셔야됩니다^^
(일주일 후)
직장상사: ㅇㅇ씨 왜 이 메소드 호출할땐 시간이 안찍히죠?? 아 그리고 조금 바꿔서 실행 직전에도 현재시간을 찍고, 실행 후에는 현재시간을 찍고나서 전체 실행시간을 찍도록 바꿔주세요. 아 그리고 xxx.xxx 패키지에서는 빼주세요.
나: 퇴사하겠습니다^^

같은 상황이 벌어지면 안되겠죠 하하하.. 

 

위같은 로그찍기를 더 큰 범위에서 보면 아래와 같은 그림처럼 애플리케이션 전체에 존재하는 모든 구조를 가로지르게 되는것이죠. 그래서 이렇게 여러 메소드 또는 클래스에 분산되어 걸쳐져있는 관심사를 횡단관심사(cross-cutting concern) 라고 합니다.

로그찍는 기능을 cross-cutting concern의 예시로 보여주는 그림

관점(Aspect)

자 그럼 위 상황에서 제가 퇴사하지 않을 수 있는 방법은 어떤 것이 있을까요?

엄청난 노가다 없이 위 작업을 수행할 수 있는 1400만 605개의 미래를 보는중

바로 관점(Aspect)을 생성하는 것입니다!

위에서 수행하는 "실행시간을 로그로 찍는다" 라는 관심사를 하나의 모듈 즉 하나의 관점(Aspect)로 만드는 것이죠! 그래서 흩어진 관심사의 로직이 변경이 되어도 모든 코드를 수정하지 않아도 되고, 심지어 기존 코드를 수정하지 않아도 됩니다. 오잉 그게 가능해요? 네~! 바로 그걸 쉽고 편하게 구현 가능하게 해주는게 바로 스프링 AOP 입니다.

 

결론

따라서 AOP란, 흩어진 관심사(cross-cutting concern)를 관점(Aspect)로 모듈화하여 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 프로그래밍 개념인 것입니다. 아직 이 문장이 이해가 안간다면 천천히 다시 읽어보시길 추천드립니다!

 

자 그럼 이 개념을 구현해놓은 스프링 AOP로 바로 넘어가볼까요??


스프링 AOP

스프링 AOP는 AOP의 개념을 구현한 AOP 구현체 입니다. 특징은 다음과 같습니다

  • 프록시 패턴 기반 (아래에서 설명)
  • 스프링 빈에만 AOP 적용 가능
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 ...)에 대한 해결책을 지원하는 것이 목적

프록시 패턴 (Proxy Pattern)

프록시(Proxy)란 "대신"이라는 의미를 가지고 있습니다. 실생활에서 보면 대변인같은 그런 느낌일수도 있겠네요. 본인이 직접 소통하지 않고 대변인을 통해 정보를 받고, 다른사람에게 정보를 전달할때도 대변인을 통해서 하니까요. 이는 프로그램에서 프록시도 똑같은 역할을 합니다. 이처럼 프록시를 활용하는 디자인 패턴을 프록시 패턴이라고 합니다!

그럼 프록시가 어떻게 활용되나요? 위 그림을 예제로 보겠습니다.

클라이언트는 request() 메소드 호출이 필요합니다.

이때 request() 메소드가 구현돼있는 RealSubject에 직접 접근하지 않고, 프록시 객체(대리자)가 대신 RealSubject객체의 메소드를 호출하고 그 반환값을 클라이언트에게 전달하게 됩니다.

 

왜 굳이 이렇게 하나요?

  1. 이 패턴을 사용하면 실제 메소드의 코드를 변경하지 않고! 호출되기전에 필요한 기능을 추가할 수 있습니다. 예를들어 위에서 말했던 로그찍기같은걸, 실제 메소드코드 변경이 하나도 없이 가능한 것이죠
  2. 캐시를 사용할 수 있습니다. 프록시가 내부캐시를 통해 데이터가 캐시에 존재하지 않는 경우에만 실제 구현 클래스에서 작업이 실행되도록 할 수 있습니다.

자 이제 글 설명은 잠깐 멈추고 실제 구현 코드 예제를 한번 볼까요?


스프링 AOP 코드 예제 (모든 메소드에서 실행시간 로그찍기)

1. 의존성(dependency)를 추가합니다

2. 관점(Aspect)를 등록합니다. (적용방식에 따라 annotation 클래스도 등록해줍니다)

@Component
@Aspect
public class DannyAspect {

    // 해당되는 모든 패키지안에있는 메소드에 적용
    @Around("execution(* com.example..*.ProfileService.*(..))")
    public Object printDuration1(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis(); 
        Object retVal = pjp.proceed(); // 메서드 호출 자체를 감쌈 
        System.out.println(System.currentTimeMillis() - begin); 
        return retVal; 
    }
    
    // @DannyCustomAnnotation 어노테이션이 붙은 메소드에만 적용
    @Around("annotation(DannyCustomAnnotation)")
    public Object printDuration2(ProceedingJoinPoint pjp) throws Throwable {
        long begin = System.currentTimeMillis(); 
        Object retVal = pjp.proceed(); // 메서드 호출 자체를 감쌈 
        System.out.println(System.currentTimeMillis() - begin); 
        return retVal; 
    }
}
// printDuration2 관점을 위한 어노테이션 클래스 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DannyCustomAnnotation {
}

3. 관점(Aspect)을 누구에게, 언제 적용할건지에 대한 세부 설정을 해줍니다

@Around, @Before, @After 등등 어노테이션에 따른 실행 시점

  • 위 코드를 보면 printDuration 관점에 @Around 어노테이션을 보실 수 있습니다. 위 표에 따라서 해당 관점은 메소드가 실행되기 전과 후에 모두 실행된다는 것을 알 수 있습니다.
  • execution(*com.example..*.ProfileService.*(..))가 의미하는 바는 com.example 아래의 패키지 경로의 ProfileService 객체에 모든 메서드에 이 Aspect를 적용하겠다는 뜻입니다.
  • annotation(DannyCustomAnnotation)가 의미하는 바는 해당 어노테이션 클래스가 붙은 메소드에만 이 Aspect를 적용하겠다는 뜻입니다.

4. 메소드에 적용

@Service
public class ProfileService {

    @DannyCustomAnnotation
    public void createProfile() {
        비즈니스로직();
    }

    public void updateProfile() {
        비즈니스로직();
    }

    @DannyCustomAnnotation
    public void deleteProfile() {
        비즈니스로직();
    }
}

설정값에 따라 어노테이션 없이도 적용이 되게할 수 있고, 어노테이션이 붙은 곳에만 적용되게 할 수 있겠죠.

여기서 가장 큰 포인트는 "실행시간을 찍는 기능"이 추가되었지만 기존 코드에는 아무 변경이 없다는 것입니다!


오늘은 이렇게 AOP와 스프링 AOP에 대해서 알아보았습니다.

정말 스프링이란.. 알면 알수록 많은 것이 보이는 것 같습니다. 읽어주셔서 감사합니다 :)

 

출처:
- Aspect-oriented programming
- Chapter 6. Aspect Oriented Programming with Spring
- Chapter 1. What Is Aspect-Oriented Programming?
- Separation of Concerns에 대해
- Separation of concerns
- [Spring] AOP(Aspect Oriented Programming)란 무엇일까?
- [Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP
728x90