티스토리 뷰
[SpringBoot] AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 개념 및 사용 방법 예제 코드 - (2/3)
망나니개발자 2021. 9. 9. 19:001. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이란?
[ AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이란? ]
프로그래밍을 하다보면 공통적인 기능이 많이 발생한다. 이러한 공통 기능을 모든 모듈에 적용하기 위해 상속을 이용한다. 하지만 Java에서는 다중 상속이 불가능하며, 상속을 받아 공통 기능을 부여하기에는 한계가 있다.
예를 들어 우리가 개발한 API의 호출 시간을 측정하고 싶다고 하자. 이를 AOP없이 구현한다면 어떻겠는가? AOP를 적용하지 않는다면 중복 코드가 발생할 소지가 있고, 코드의 변경이 필요하면 여러 코드에 종속적으로 변경이 필요할 것이며, 핵심적인 비지니스 로직에 호출 시간 측정이라는 부수적인 로직이 추가되어 가독성과 효율성이 떨어지는 등의 문제가 발생할 수 있다. 이러한 문제점을 해결하기 위해 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)이 등장하게 되었다. AOP는 새로운 프로그래밍 패러다임이 아니라 OOP(Object Oriented Programming, 객체 지향 프로그래밍)를 돕는 보조적인 기술로, 핵심적인 관심 사항(Core Concern)과 공통 관심 사항(Cross-Cutting Concern)으로 분리시키고 각각을 모듈화 하는 것을 의미한다. 예를 들어 우리가 개발한 API중에 회원 가입 API가 있다면, 핵심적인 관심 사항은 회원
가입이라는 비지니스 로직이 될 것이고, 공통 관심 사항은 호출 시간 측정 로직이 될 것이다.
[ AOP(Aspect Oriented Programming)의 장점 ]
- 공통 관심 사항을 핵심 관심사항으로부터 분리시켜 핵심 로직을 깔끔하게 유지할 수 있다.
- 그에 따라 코드의 가독성, 유지보수성 등을 높일 수 있다.
- 각각의 모듈에 수정이 필요하면 다른 모듈의 수정 없이 해당 로직만 변경하면 된다.
- 공통 로직을 적용할 대상을 선택할 수 있다
2. 패키지 기반, 어노테이션 기반으로 AOP 사용해보기
[ AOP 사용해보기 ]
다른 요소들과 마찬가지로, AOP를 적용하기 위해서는 먼저 의존성을 추가해주어야 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
AOP 의존성을 추가하고 빌드를 하였으면 AOP를 활성화하겠다는 어노테이션을 추가해주어야 한다. SpringBoot의 애플리케이션 클래스에 @EnableAspectJAutoProxy 어노테이션을 추가해주도록 하자.
@EnableAspectJAutoProxy
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
위의 두 과정을 거치면 AOP를 적용하기 위한 준비가 끝난 것이다. 어디에 AOP를 적용하지 말고, 실행 시간을 측정하기 위한 Aspect 클래스부터 개발해보도록 하자.
@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {
// 모든 패키지 내의 controller package에 존재하는 클래스
@Around("execution(* *..controller.*.*(..))")
public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
// 해당 클래스 처리 전의 시간
StopWatch sw = new StopWatch();
sw.start();
// 해당 클래스의 메소드 실행
Object result = pjp.proceed();
// 해당 클래스 처리 후의 시간
sw.stop();
long executionTime = sw.getTotalTimeMillis();
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
String task = className + "." + methodName;
log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");
return result;
}
}
우선 Aop 클래스로 설정하기 위해 @Aspect 어노테이션을 추가해주었으며, Spring의 빈으로 등록하기 위해 @Component 어노테이션을 추가해주었다. 그리고 우리가 하고자 하는 것은 모든 API의 실행 시간을 측정해주는 것이므로 StopWatch를 생성하여 측정을 시작하였다. 그리고 pjp의 proceed를 통해 실제 핵심 로직을 실행하여 Object 클래스로 메소드의 결과를 받았다. 이후에 StopWatch를 중단하여 실행 시간을 밀리세컨드로 계산하여 로그를 출력하고 함수를 종료시키고 있다.
이제 위와 같은 기능을 적용할 지점을 선정해야 하는데, 크게 패키지 기반으로 하는 방법과 어노테이션 기반으로 하는 방법이 있다.
패키지기반으로 AOP 적용하기
먼저 Controller 패키지에 해당 기능을 적용하기 위해서는 다음과 같은 @Around를 적용해줄 수 있다.
@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {
// 모든 패키지 내의 controller package에 존재하는 클래스
@Around("execution(* *..controller.*.*(..))")
public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
// 해당 클래스 처리 전의 시간
StopWatch sw = new StopWatch();
sw.start();
// 해당 클래스의 메소드 실행
Object result = pjp.proceed();
// 해당 클래스 처리 후의 시간
sw.stop();
long executionTime = sw.getTotalTimeMillis();
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
String task = className + "." + methodName;
log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");
return result;
}
}
어노테이션 기반으로 AOP 적용하기(클래스 레벨, 메소드 레벨)
반면에 어노테이션 기반으로 적용하기 위해서는 AOP를 처리하기 위한 어노테이션을 다음과 같이 만들어주어야 한다.
(커스텀 어노테이션 만들기에 대해 이해가 여기를 참고해주세요!)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ExecutionTimeChecker {
}
위에서 @Target은 어노테이션이 적용될 레벨을 의미한다. 이번에는 클래스 또는 메소드에 적용하고자 TYPE, METHOD를 추가해주었다.
그 다음에는 해당 어노테이션이 적용되도록 @Around를 수정해야 하는데, 다음과 같이 수정해주면 된다. 여기서 핵심은 @Around에 within이 사용되었다는 것이다. 그리고 해당 메소드를 호출해보면 실행 시간을 측정하는 AOP가 클래스 레벨에는 물론 메소드 레벨까지 정상적으로 실행됨을 확인할 수 있다.
@Aspect
@Component
@Log4j2
public class ExecutionTimeAop {
@Around("@within(com.mang.atdd.membership.aop.ExecutionTimeChecker)")
public Object calculateExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
// 해당 클래스 처리 전의 시간
StopWatch sw = new StopWatch();
sw.start();
// 해당 클래스의 메소드 실행
Object result = pjp.proceed();
// 해당 클래스 처리 후의 시간
sw.stop();
long executionTime = sw.getTotalTimeMillis();
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
String task = className + "." + methodName;
log.debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");
return result;
}
}
만약 AOP를 적용하지 않은 상태에서 실행 시간 측정 단위를 밀리세컨드가 아닌 세컨드로 변경한다고 하면 어떻게 해야 할까? AOP를 적용하지 않았다면 중요하지도 않은 비지니스 로직도 아님에도불고하고, 관련 로직의 모든 코드를 수정했어야 할 것이다. 하지만 AOP를 적용함으로써 핵심 로직에 대한 수정 없이 편리하기 이를 처리할 수 있게 되었다.
관련 포스팅
- AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 이해 - (1/3)
- AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)의 개념 및 사용 방법 예제 코드 - (2/3)
- Spring의 AOP 프록시 구현 방법(JDK 동적 프록시, CGLib 프록시)과 @EnableAspectJAutoProxy의 proxyTargetClass - (3/3)
'Spring' 카테고리의 다른 글
[Spring] @ConfigurationPropertiesScan을 이용한 설정 프로퍼티 클래스(@Configuration Properties)의 빈 등록 (0) | 2021.09.19 |
---|---|
[SpringBoot] final 변수를 갖는 클래스에 프로퍼티(Properties) 설정 값 불러오기, 생성자 바인딩(Constructor Binding) (11) | 2021.09.12 |
[Spring] TDD로 멤버십 삭제 및 포인트 적립 API 구현 예제 - (5/5) (17) | 2021.08.29 |
[Spring] TDD로 멤버십 전체/상세 조회 API 구현 예제 - (4/5) (7) | 2021.08.26 |
[Spring] TDD로 멤버십 등록 API 구현 예제 - (3/5) (24) | 2021.08.22 |