티스토리 뷰
스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 김영한 인프런 강의 참고
이전 포스팅
- 2023.02.25 - [Web Programming/Spring&Spring Boot] - [Spring Boot] 입문 - Spring Data JPA 맛보기
- 2023.02.24 - [Web Programming/Spring&Spring Boot] - [Spring Boot] 입문 - Spring Boot 프로젝트에 JPA 적용하기
- 2023.02.23 - [Web Programming/Spring&Spring Boot] - [Spring Boot] 입문 - JPA를 사용하는 이유
Goals
- AOP가 필요한 상황
- AOP란?
- AOP 적용해보기
- AOP 용어 정리
- 스프링의 AOP 동작 방식
AOP가 필요한 상황
AOP가 무엇인지 알아보기 전에 먼저 어떤 상황에서 AOP라는 기술이 필요한 지 예시를 들어보겠다.
만약 프로젝트를 진행하던 중, 회원 조회 메서드의 호출 시간을 측정해야 한다고 해보자.
service/MemberService.java
// ...
public List<Member> findMembers() {
long start = System.currentTimeMillis();
try{
return memberRepository.findAll();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMembers " + timeMs + "ms");
}
}
그런데 위 시간 측정 로직을 갑자기 모든 메서드 적용해야 하는 상황이 됐다.
service/MemberService.java
// ...
/**
* 회원가입
*/
public Long join(Member member){
long start = System.currentTimeMillis();
try{
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMembers " + timeMs + "ms");
}
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers() {
long start = System.currentTimeMillis();
try{
return memberRepository.findAll();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMembers " + timeMs + "ms");
}
}
서비스 로직만 하더라도 벌써 혼란스러워 졌다. 이걸 모든 메서드에 적용하려고 하니 단순 코드의 반복을 없애고 싶다. 하지만 생각보다 쉽지 않다. 비즈니스 로직이 시간 측정 로직 사이에 껴있고, try
문으로 예외도 처리해 줘야 한다.
위와 같은 상황의 문제점을 찾아보자.
- 회원 가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심사가 아니다.
- 시간을 측정하는 로직은 공통 관심 사다.
- 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
- 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다
핵심 관심사(core concern) : 핵심 비즈니스 로직
공통 관심사(cross-cutting concern) : 보통 횡단 관심사라고 하며, 다수의 모듈에 공통적으로 나타나는 부분
위 상황을 말끔하게 해결해주는 기술이 바로 AOP다.
AOP란?
AOP(Aspect-Oriented Programming)는 프로그램 구조에 대한 또 다른 사고 방식을 제공하여 OOP(Object-Oriented Programming)를 보완한다. OOP에서 모듈화의 핵심 단위는 클래스인 반면, AOP에서는 모듈화 단위가 관점(aspect)이다.
Aspect는 여러 유형과 객체를 가로지르는, 예를 들어서 로깅, 트랜잭션, 보안, 캐싱 관리와 같이 프로젝트 전반적으로 공통되고 부가적인 문제들를 모듈화 함으로써 객체 지향적인 설계를 가능하게 한다.
쉽게 말해, 어떤 로직을 기준으로 핵심 관심사와 공통 관심사로 나눠보고, 이를 모듈화 하겠다는 의미인데, AOP에서는 이 공통 관심사를 모듈화하며, 이를 관점(Aspect)라고 한다.
AOP에서 사용하는 용어들이 있다. 잘 와 닿지 않지만, API 문서를 참고할 때 해당 용어들로 설명되어 있기 때문에 어쩔 수 없이 알고는 있어야 한다.
주요 용어 정리
- Aspect : 여러 클래스에 걸쳐 있는 관심사(횡단 관심사)의 모듈화
- Join point : 프로그램이 실행되는 여러 위치. 스프링 프레임워크에서는 스프링 빈의 모든 메서드에 해당할 수 있으며, 코드 상에서는 호출된 객체의 메서드를 나타냄. Spring AOP에서는 항상 메서드의 실행이 된다.
- Pointcut : Aspect의 적용 위치 지정자(Pointcut 표현식이 있으며, 여기에 매칭되는 모든 Join point들에 aspect가 적용됨)
- Advice : Pointcut에 언제, 무엇을 적용할지 정의한 메서드로 어노테이션을 통해 5가지 중 하나로 지정할 수 있다.
- Before: Join point 전에 실행
- After: Join point 후에 실행
- After Returning: Join point가 정상적으로 종료된 후에 실행
- After Throwing: Join point에서 예외가 발생한 후에 실행
- Around: Join point 전후에 모두 실행되며, Join point 자체의 실행을 제어할 수 있음
- Target object : 하나 이상의 Aspect에서 Advice하는 객체. Advice object라고도 한다.
- Weaving : Pointcut에 해당된 Join Point들에 Advice가 적용된 객체(Target object)를 생성한 뒤 프로세스되는 일련의 모든 과정. 뒤에서 설명하겠지만 Proxy 객체를 생성하는 과정이며, Spring AOP는 Target object의 메서드가 호출되는 시점에 수행하므로 Runtime Weaving 방식을 기반으로 한다.
이렇게 보면 개념이 어려우니 직접 적용해 보면서 알아보자.
AOP 적용해보기
먼저 AOP들을 모아놓는 패키지를 하위에 생성하고 시간 측정 AOP 클래스를 작성한다.
aop/TimeTraceAop.java
// ...
@Component
@Aspect
public class TimeTraceAop {
@Around("execution(* com.example.practice.service..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
TimeTraceAop
클래스를 스프링 빈으로 등록하기 위해 @Component
를 붙여 준다. (보통 위와 같은 특수한 클래스들은 SpringConfig
에서 따로 관리하는 것이 좋다)
execute
메서드는 하나의 Advice가 된다.
@Aspect
: 이 클래스를 이제 AOP에서 사용하겠다는 의미로 Aspect 지정@Around
: Advice의 5가지 종류 중 하나로, 지정된 패턴에 해당하는 메서드가 실행되기 전, 후 모두에서 동작 가능 (공식 문서에서는 Around advice가 다른 4가지 종류의 advice 기능을 포함하므로 가장 강력하다고 소개한다. 때문에 특별한 경우가 아니면 사용을 자제해야 한다고 하지만, 실무에서는 가장 많이 사용한다고 한다)execution()
: Pointcut으로 특정 메서드를 지정하는 패턴을 작성할 수 있는 방법. 여기에 매칭된 모든 메서드가 Joint point가 된다.
패턴 : [접근제어자] 리턴타입 [패키지&클래스.] 메서드이름(파라미터) [throws 예외]
위 패턴을 해석해 보면,- 리턴타입 :
*
(모두) - 패키지&클래스 :
com.example.practice.service
하위 모든 패키지&클래스 - 메서드이름 :
*
(모든 메서드) - 파라미터 :
(..)
(0개 이상)
- 리턴타입 :
ProceedingJoinPoint
: 스프링 컨테이너가 넘겨준 핵심 관심사(위 패턴에 해당하는 Joint point) 메서드 정보joinPoint.proceed()
: Joint point를 이 위치에서 호출. 반환 값으로는 Joint point의 실행 결과 값이 담긴Object
테스트를 실행해 보면,
MemberService
의 모든 메서드에서 시간 측정 로직이 적용된 것을 확인할 수 있다.
만약 모든 메서드에 적용해보고 싶으면 execute()
의 문자열에서 service
를 지워주고 ..*
만 남기면 된다.
→ @Around("execution(* com.example.practice..*(..))")
이는 practice
패키지의 모든 메서드에 적용하라는 의미다.
로그를 보면, 어떤 클래스의 메서드들이 어떤 순서로 호출되는지 확인할 수 있다.
이제 우리는
위와 같던 상황을 아래와 같이 바꿀 수 있다.
결과적으로 위의 문제들을 다음과 같이 해결할 수 있다.
- 회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.
- 핵심 관심 사항을 깔끔하게 유지할 수 있다.
- 변경이 필요하면 공통 관심 사항 로직만 변경하면 된다.
- 원하는 적용 대상을 선택할 수 있다.
스프링의 AOP 동작 방식
어떻게 비즈니스 로직을 시간 측정 로직 사이에 끼워 넣을 수 있었을까? 우리가 AOP를 적용하기 전에는 memberController
가 memberService
를 의존하는 의존 관계가 직접 맺어져 있었다.
하지만 AOP를 적용하면 스프링 컨테이너가 프록시 객체(bean)를 생성한 뒤 이 Proxy 객체를 DI해서 의존 관계를 간접적으로 맺어버린다. 즉 DI를 가로채는 것이다.
이 AOP Proxy 객체는 우리가 위에서 정의한 TimeTraceAop
가 스프링 컨테이너에 등록된 스프링 빈이며, 실제 memberService
를 의존하는 의존 관계를 맺게 된다.
memberController
는 프록시 memberService
를 주입 받으므로, 메서드 호출 시에도 Proxy 객체의 메서드들을 호출하게 된다. 콘솔로 memberController
가 실제로 주입된 Proxy를 호출하는 지 확인할 수 있다.
controller/MemberController.java
// ...
@Controller
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberService;
System.out.println("memberService = " + memberService.getClass());
}
// ...
MemberService
뒤에 $$SpringCGLIB
이 붙은 것을 확인할 수 있는데, SpringCGLIB이라는 라이브러리가 생성한 Proxy 객체라는 의미다.
💡 CGLib은 Code Generator Library의 약자로, 클래스의 바이트코드를 조작하여 Proxy 객체를 생성해주는 라이브러리
(Proxy 객체 생성에 대해 자세히 알고 싶으면 https://gmoon92.github.io/spring/aop/2019/04/20/jdk-dynamic-proxy-and-cglib.html )
그럼 실제 memberService
는 언제 호출되는 것일까? 바로 프록시 memberService
에서 joinPoint.proceed()
메서드가 호출되는 시점이다.
결국 모든 메서드를 JoinPoint로써 AOP를 적용하게 되면 다음과 같은 의존 관계가 된다.
이러한 AOP 기술이 가능한 이유에는 근본적으로 DI에 있다. 우리가 만약 new 키워드를 통해 의존 관계를 직접 맺어 줬다면, 스프링 컨테이너가 프록시 기술을 통해 의존 관계를 변경할 수 없었을 것이다.
스프링 컨테이너가 모든 스프링 빈들을 관리하고, 의존 관계를 맺어주기 때문에 AOP같은 기술의 응용이 가능한 것이다.
이것으로 Spring Boot 입문은 끝났다. 김영한님의 무료 강의 덕분에 큰 틀에서의 Spring Boot를 이해할 수 있었다. 처음 Spring을 공부하시는 분이라면 적극 추천.
Reference
'Spring&Spring Boot' 카테고리의 다른 글
[Spring] Servlet에서 JSP까지, 그리고 한계 (feat. MVC 패턴) (7) | 2024.10.09 |
---|---|
스프링이란? 좋은 객체 지향 설계란? (SOLID, 스프링 컨테이너, IoC, DI) (3) | 2024.09.05 |
[Spring Boot] Spring MVC CRUD를 위한 방명록 프로젝트 - 2 (0) | 2023.04.29 |
[Spring Boot] Spring MVC CRUD를 위한 방명록 프로젝트 - 1 (0) | 2023.04.27 |
[Spring] Thymeleaf 5가지 기본 표현식/자주 쓰는 구문 정리 (0) | 2023.04.22 |
- Total
- Today
- Yesterday
- jsp
- git branch
- git
- 운영체제 반효경
- JPA
- Spring
- 지옥에서 온 git
- Spring Boot
- spring mvc
- Gradle
- 스프링 컨테이너
- 생활코딩 javascript
- 스프링 테스트
- 파이썬 for Beginner 솔루션
- Python Cookbook
- git merge
- 쉽게 배우는 운영체제
- Spring Data JPA
- 패킷 스위칭
- Thymeleaf
- 쉘 코드
- 방명록 프로젝트
- 스프링 mvc
- 파이썬 for Beginner 연습문제
- 스프링
- Do it! 정직하게 코딩하며 배우는 딥러닝 입문
- 김영환
- Computer_Networking_A_Top-Down_Approach
- 프로그래머스
- 선형 회귀
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |