일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Nodejs
- 카카오 코테
- 시스템호출
- 프로그래머스
- @Component
- 컴포넌트스캔
- C언어
- python
- 카카오
- AWS
- 가상면접사례로배우는대규모시스템설계기초
- nestjs typeorm
- 코딩테스트
- nestjs auth
- 구조체배열
- git
- 카카오 알고리즘
- TypeORM
- C++
- @Autowired
- thymeleaf
- 파이썬
- nestJS
- 코테
- spring boot
- 스프링
- OpenCV
- Spring
- 알고리즘
- 해시
- Today
- Total
공부 기록장 💻
[Spring] AOP의 개념과 필요한 이유, 예제 실습 (Aspect Oriented Programming, @Aspect, @Around) 본문
[Spring] AOP의 개념과 필요한 이유, 예제 실습 (Aspect Oriented Programming, @Aspect, @Around)
dream_for 2023. 1. 18. 11:31인프런 "스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술" 정리
AOP (Aspect Oriented Programming) 이란?
AOP란, Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라 불린다.
쉽게 말해 어떤 로직을 기준으로 공통 관심 사항(corss-cutting concern)과 핵심 관심 사항(core concern) 으로 관점을 분리하여 보고, 이 관점을 기준으로 각각 모듈화하는 것을 말한다.
AOP가 필요한 상황
아주 간단한 예를 들어, 이전에 작성했던 회원 예제에서 모든 메서드에 대한 호출 시간을 측정하는 시간 측정 로직을 추가한다고 가정해보자. 각 메서드의
시간 측정 로직을 수행하는 코드를 각 컨트롤러의 각 메서드, 서비스의 각 메서드, 레포지토리의 각 메서드에 이를 추가해야 될지도 모르겠다. 다음과 같이 말이다.
위의 방식대로 구현을 해보자.
먼저 회원 등록을 수행하는 join() 메서드의 수행 시간을 milli second 단위로 측정하기 위해서는 다음과 같이 코드로 구현해낼 수 있다.
메서드 실행 시작 시점인 start와 실행 종료 시점인 finish 의 차이를 구하면 우리는 시간을 구할 수 있게 된다.
위 메서드를 3번 실행하면 아래와 같이 join = ~ ms 가 콘솔에 출력되는 것을 확인할 수 있다.
문제는 무엇인가?
위의 코드를 작성하는 겨우 발생하는 문제는, 위와 같은 코드를 모든 메서드에서 구현해야 한다는 점이다. 만약 구하려는 값이 다른 값으로 변경된다면, 모든 메서드에서 해당 코드의 일부를 변경해야 한다는 점이다. 혹은 더이상 해당 로직이 필요 없어지는 순간이라면 어떨까. 메서드의 개수가 수천 개 이상이 되는 프로그램에서 이와 같인 작업을 반복할 수는 없다. 즉, 유지 보수가 어려워진다.
회원 가입에 시간을 측정하는 기능은 해당 메서드의 핵심 관심 사항이 아니다. 시간 측정 로직은 모든 메서드의 공통 관심 사항으로, 여기서 우리는 AOP 를 적용해볼 수 있다.
TimeTraceAop
우리는 시간을 측정하는 로직, 즉 공통 관심 사항을 따로 분리하여 이를 필요한 곳에 적용할 수 있다.
hellospring 디렉터리 내에 aop 패키지를 만들고, 시간을 측정하는 관심 사항만을 구현하는 TimeTraceAop라는 이름의 클래스를 만들어보자.
아래와 같이 구현을 해보자.
우리의 공통 관심 사항이라는 의미를 지니는 @Aspect 어노테이션과, 스프링 빈에 등록할 컴포넌트임을 @Component 어노테이션으로 명시하자.
@Around 어노테이션 내 파라미터로는 시간 측정이 메서드 전후로 join point에서 실행될 대상 메서드들이 포함된 디렉터리 경로를 적어준다.
execute() 메서드에는 PreceedingJoinPoint 클래스의 객체를 파라미터로 추가하고, 구체적으로 수행할 코드를 내부에 작성 해준다.
Configuration에는 TimeTraceAop 객체를 생성하는 메서드를 추가해주도록 하자.
이제 앱을 실행한 후, 로컬 서버에서 두 회원의 등록을 마치고, 회원 목록을 조회해보도록 하자.
콘솔에 나타난 텍스트를 살펴보면, execution 과 함께 join point, 즉 시간 측정 로직이 수행된 메서드의 이름이 나타나 있고, END 부분에는 측정된 시간도 함께 출력되었다.
아래에서 빨간색 밑줄은 회원 등록 수행의 시작과 끝을, 파란색 밑줄은 회원 목록 조회의 시작과 끝을 각각 표현한 것이다.
위에서 작성한 TimeTraceAop 를 사용하면, 각 메서드에 시간 측정을 하는 로직을 개별적으로 구현하지 않아도 되므로, 핵심 관심 사항을 깔끔하게 유지할 수 있다. 시간을 측정하는 로직을 별도의 공통 로직으로 만들어 각 메서드의 핵심 관심 사항과 시간을 측정하는 공통 관심 사항을 분리할 수 있으며, 시간 측정 로직에 변경이 필요한 경우 해당 클래스 내 구현 코드 일부만 변경하면 된다. 위의 예제에서는 모든 메서드에 적용을 했지만, 실제로는 원하는 적용 대상을 선택하는 것도 가능하다.
아래는 Class A에는 Aspect X, Y, Z를, Class B에는 Aspect X와 Z를, 그리고 Class C에는 Aspect X와 Y를 적용한 것이라 볼 수 있다.
AOP 동작 방식
AOP를 적용하기 이전에는 컨트롤러에서 비즈니스 로직을 담당하는 서비스를 직접 호출한 반면,
AOP를 적용한 후에는 아래와 같이 실제 memberService의 코드를 복제하여 proxy 서비스를 통해 AOP가 실행되고 joinPoint가 실행된 후에 실제 memberService가 자신의 역할을 하게 된다.
MemberController 생성자에서 member Service의 클래스를 출력하여 확인해보자.
MemberService 뒤에 어떠한 코드가 나타나는 것을 확인할 수 있다. 이는 프록시 서비스의 복제 방법 중 하나를 나타낸 것이라 볼 수 있다.
AOP 적용 후 전체적인 구조를 보면, 각 컨트롤러와 서비스, 레포지토리가 모두 프록시를 만들어 작용하게 된다.
[참고자료]
https://engkimbs.tistory.com/746
https://code-lab1.tistory.com/193