관점지향프로그래밍(AOP) 에서 관점(Aspect)은 무슨 뜻일까?
스프링 프레임워크의 주요 특징은 무엇인가? 를 얘기하면 빠지지않게 나오는것 중 하나인 관점지향프로그래밍(Aspect Orient Programing)에 관한 글입니다. 바로 들어가죠.
AOP에서 말하는 Aspect 란?
위 그림에서 사용자, 개발자, 운영자는 각각 자기만의 관점이 있습니다. 여기서 사용자의 관점이 주 업무로직이 되고, 개발자&운영자가 원하는 부분은 사이드적인 부분이라 볼 수 있겠죠.
이처럼 프로그램을 만들며 사용자가 요구한 로직 뿐아니라, 개발자나 운영자에게 필요한 로직도 만들어야 하는데 '관점 지향 프로그래밍'에서 '관점(Aspect)'은 이러한 부분들을 말합니다.
즉 이러한 '코어 업무 외 의 업무들을 주업무 로직과 어떻게 분리하고, 결합시킬것인가' 에 대한 방법론이 AOP 라고도 말할 수 있겠습니다.
위 그림에서 컨테이너 박스는 주업무로 만들어진 객체들(Primary Concern)이고, 노란색 선들이 다른 관점의 업무들(Cross-cutting Concern)을 나타냅니다. 맨 왼쪽의 원은 Proxy 클래스를 나타냅니다.
로그처리, 보안처리 등을 잘 생각해보면 주 메소드들의 위와 아래에 껴들어가게 된다. 프로그램의 흐름과 크로스로 뺐다&꼈다 해야하는(위 그림을 보며 개념을 시각화 해봅시다) 로직들이기 때문에 이러한 다른 관점의 코드들을 Cross-cutting Concern 이라고 부릅니다.
그렇다면 주 업무가 진행되는 와중에 로그를 보고 싶다면 어떻게 해야할까요? 그때 마다 코드를 수정 해야 할까요?
이렇게 관점이 다른 코드들을 쉽게 뺐다, 꽂았다 할 수 있는 방법을 생각하다 나온 방법론이 AOP 입니다.
예제로 개발자를 위한 로그가 섞여있는 코드를 보고 가겠습니다.
public class NewlecExam implements Exam {
public int total() {
long start = System.currentTimeMillis();
SimpleDateFormat dayTime = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");
String str = dayTime.format(new Date(start));
System.out.println(str); // 현재 시간 출력을 출력하는 부업무(로그)
/*--------------------------------*/
int result = kor + eng + math + com; //주 업무
/*--------------------------------*/
long end = System.currentTimeMillis();
String message = (end - start) + "ms 걸림";
System.out.println(message);
return result;
}
}
이처럼 주업무의 목적(관점)과 다른 목적(로깅)을 가진 코드가 섞여 있으면 코드도 복잡해지고, 유지보수 측면에서도 힘들어지겠죠.
그렇기 때문에 분리가 필요합니다.
AOP의 구현은 Core Concern과 Cross-cutting concern을 분리하고 Proxy를 거쳐서 함수가 호출되도록 하는 방식으로 구현하게 됩니다. 주업무와 다른 관점들을 분리시킨 코드를 볼까요?
import org.springframework.stereotype.Component;
@Component
public class NewlecExam implements Exam {
private int kor, eng, math, com; // 실제 값이 할당되어야 함
// 주 업무 로직만 남김
public int total() {
// 성적 합산 로직
int result = kor + eng + math + com;
return result;
}
// Setter 메소드는 생략했습니다.
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceLoggingAspect {
// 주업무 메소드에 대한 Pointcut 정의
@Pointcut("execution(* NewlecExam.total(..))")
public void performanceLog() {}
// 주업무 메소드 실행 전에 호출
@Before("performanceLog()")
public void logBefore(JoinPoint joinPoint) {
long start = System.currentTimeMillis();
SimpleDateFormat dayTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = dayTime.format(new Date(start));
System.out.println("Start time: " + str);
LoggingUtil.startTime.set(start); // 시작 시간을 스레드 로컬에 저장
}
// 주업무 메소드 실행 후에 호출
@After("performanceLog()")
public void logAfter(JoinPoint joinPoint) {
long end = System.currentTimeMillis();
long start = LoggingUtil.startTime.get(); // 스레드 로컬에서 시작 시간 가져오기
String message = (end - start) + "ms 걸림";
System.out.println("End time: " + message);
LoggingUtil.startTime.remove(); // 스레드 로컬에서 시작 시간 제거
}
}
// 스레드별로 시작 시간을 관리하기 위한 유틸리티 클래스
class LoggingUtil {
static ThreadLocal<Long> startTime = new ThreadLocal<>();
}
@Pointcut 어노테이션을 사용해 로깅이 필요한 메소드를 지정하고, @Before와 @After 어노테이션으로 메소드 실행 전후의 로깅 로직을 지정합니다.
이렇게 주업무 로직과 부업무 로직을 분리하여 추후에 다른 부업무 로직(주로 로깅)이 생기더라도 주업무를 담당하는 코드를 수정하지 않고 추가할 수 있게 됩니다. 확장, 유지보수가 쉬워지는 장점이 있죠.
위의 Proxy 를 통한 코드분리 방법은 사실 스프링에서만 가능한게 아니라 자주 쓰이는 리펙토링 방법(디자인패턴)중 하나 입니다. (스프링, 자바를 떠나 다른 언어, 프레임워크에도 적용 가능합니다)
https://refactoring.guru/ko/design-patterns/proxy
프록시 패턴
/ 디자인 패턴들 / 구조 패턴 프록시 패턴 다음 이름으로도 불립니다: Proxy 의도 프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴입니다. 프록시는 원래 객체
refactoring.guru
이러한 기법을 Spring 프레임워크는 Annotation 을 통해( IOC컨테이너를 통한 DI) AOP를 손쉽게 구현할 수 있도록 도와주기 때문에 꾸준히 사랑받을 수 있는거죠.
여기까지 Spring 의 주요 특징중 하나인 AOP 에 관한 설명이었습니다. 감사합니다!