6. AOP
1. AOP가 필요한 상황
2. AOP 적용
1) AOP가 필요한 상황
AOP의 본격적인 이해에 앞서, 상황을 예로들어 어떤 경우에 AOP가 사용 되는지 알아보자!
• 어떤 경우에 AOP를 사용하나?
- 모든 메서드의 호출 시간 측정
- 공통 관심 사항 (cross-cutting concern) vs. 핵심 관심 사항 (core concern)
- 회원 가입 시간, 회원 조회 시간 측정
• 회원 조회(findMembers), 가입(join) 시간 측정 추가 ( in MemberService)
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("join = " + timeMs + "ms"); //메서드 명 + 걸린 시간 출력
}
}
- 기존의 join 메서드를 약간 수정해줬다.
- 원리는 로직 끝나는 시간 - 로직 시작 시간으로 간단하게 계산하는 방식
✔ 이때 시간 찍기위해 >> System.currentTimeMillis() 사용 [ms 단위로 시간 측정해줌]
- 로직 끝나는 시간은 try가 끝난 이후 finally 문에서 실행해준다.
➤ 마찬가지로 findMembers에도 똑같은 짓 반복해야 함^^;
public List<Member> findMembers(){
long start = System.currentTimeMillis(); //로직 시작 시간 찍기
try {
return memberRepository.findAll(); //List형으로 반환
}finally {
long finish = System.currentTimeMillis(); //로직 끝나는 시간 찍기
long timeMs = finish - start; //로직 돌아가는데 걸린 시간 계산
System.out.println("findMembers = " + timeMs + "ms"); //메서드 명 + 걸린 시간 출력
}
}
➤ 클래스마다 시간 측정 로직을 하나하나 붙여줌 → 문제 상황 발생
✔ 문제상황
1, join, findMembers가 try문 안에 들어가고 부가 기능인 시간 측정이 더 눈에 띄는 주객전도 상황... 핵심 관심 사항 X
2. 시간 측정 로직은 just 공통 관심 사항
3. But 공통 관심 사항 + 핵심 관심 사항(=핵심 비즈니스 로직)이 섞여서 혼란 ... 유지 보수 당연 어렵지
4. 그렇다고 시간 측정 로직을 공통으로 빼? 불가능...
5. 만약 시간 측정 로직을 수정하고 픔 >> 전부 찾아가서 변경^^
... ಠ_ಠ 이걸 계속 하라고?... 장난하쉼?... ➤➤➤ 그래서 등장했다 AOP !!!
2) AOP의 적용
문제 상황이 어떤지 알았으니, 이제 어떻게 쓰여서/ 어떻게 작동하는지 알아보자!
• 배경) 비즈니스 컴포넌트 개발시 중요한 원칙
1) 낮은 결합도 : DI(의존성 주입)을 통해 컴포넌트 구성 객체들의 결합도 낮출 수 있음 ⇒ 의존관계 변환 easy
2) 높은 응집도 : 핵심 비즈니스 로직을 분리하여 유지보수에 용이하게 하고, 부가적이고 반복적으로 등장하는 코드들은 따로 몰아서 효율적으로 관리하자 ⇒ AOP 담당
출처: https://heidish.tistory.com/72?category=889082
• AOP
- 정의 : Aspect Oriented Programming [관점 지향 프로그래밍]
⇒ 공통 관심 사항 /// 핵심 관심 사항으로 분리하는 것 ➤ "관심 분리"
- 즉 앞서 나온 주객전도 상황을 방지해주는 것이 바로 AOP가 하는 일
- 공통 관심 사항을 한 곳에 다 모아서 구현한 후, 원하는 곳에 적용하는 것!
요런 개념
• 시간 측정 로직의 AOP 구현
1. AOP 패키지 생성 (hellospring 하위에) >> 그 아래 TimeTraceAop 클래스 생성
2. AOP 구현
- AOP로 쓰기 위해선 @Aspect Annotation 필수!
@Aspect
public class TimeTraceAop {
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
//구현 로직은 전과 동일
long start = System.currentTimeMillis();
//어떤 메서드를 call 하는지 확인하기
System.out.println("START: " + joinPoint.toString());
try {
//proceed = 다음 메서드로 진행시킴
Object result = joinPoint.proceed();
return result;
}finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
//메서드 이름 +
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
♘ joinPoint
- 메서드가 호출될 때마다 joinpoint로 정보 날라감
- 호출시 마다 중간에서 intercept가 발생함 → 중간에 인터셉트해서 처리해주는 기술이 AOP임
- 제공하는 메소드 中 getSignature() : 호출되는 메소드에 대한 다양한 정보를 Signature 객체로 넘김
- 과정) 클라이언트가 비즈니스 메소드 호출 → 스프링 컨테이너가 JoinPoint 객체를 생성 → 메소드 호출 관련 모든 정보를 객체에 저장 → execute 호출 시 인자로 넘김
♘ ProceedingJoinPoint (interface)
- JoinPoint를 상속받은 클래스에 proceed() 메소드가 더해진 것
- 클라이언트의 요청을 가로챘으니 클라이언트가 호출한 비즈니스 메소드를 호출해 줘야함
- proceed() 메소드를 통해 비즈니스 메소드 호출 가능
3. 작성한 TimeTraceAop를 스프링 빈으로 등록 ⇒ Annotation 이용 / 스프링 빈에 직접 등록
1) @Component Annotation 이용 (Component Scan)
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimeTraceAop {
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
...
2) 스프링 빈에 직접 등록 (SpringConfig) >> 사용 多 (∵ 특별한 경우이므로)
//SpringConfig
/**
* TimeTraceAop 스프링 빈으로 직접 등록
*/
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
4. TimeTraceAop 클래스 안에 @Around Annotation 추가
(➤ 공통 관심 사항을 어디에 적용해 줄지 타겟팅 해줌! )
@Around("execution(* 패키지명..*(..))")
- 패키지 하위에 전부 적용함
- 경우에 따라 원하는 클래스명만 정의 가능
@Component
- 스프링 컨테이너가 작동하며 클래스를 스캔해서, @Component Annotation이 붙은 클래스들은 자동으로 객체 생성해 스프링 빈에 올림
@Aspect
@Component
public class TimeTraceAop {
@Around("execution((* practice.spring.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
...
5. 서버를 돌려보자
웹 브라우저를 띄운 후, 회원 목록을 클릭하자
회원 목록 조회와 관련된 요소들(Controller, Service, Repository)이 순서대로 전부 실행되고, 각 소요시간이 출력됨을 알 수 있다. (중간에 직접 작성한 시간 측정 코드가 들어가있다)
+) Advice
- 공통으로 등장해 AOP로 분리한 코드(=공통 관심 사항)에 해당
- 5가지 동작 시점이 존재 : before, after, after-returning, after-throwing, around
- before : 비즈니스 메소드 실행 전 동작
- After - Returning : 비즈니스 메소드 성공적 리턴 후 동작 (메소드 내부에 리턴값 존재해야 함)
- After - Throwing : 비즈니스 메소드 실행 중 실패 (예외 발생)시 동작 (catch부분)
- After : 비즈니스 메소드 실행 후 무조건 실행 (finally)
- Around : 메소드 호출 자체를 가로채서(intercept) 비즈니스 메소드 특정 실행 시점에 로직 삽입 가능
• AOP가 하는 일_최종_최최최종
- 예시에서 볼 수 있듯, 회원 가입/회원 조회... 핵심 관심 사항(core concern) ⇔ 공통 관심 사항(cross cutting concern)
을 분리 해준다. (시간 경과 측정 로직 ⇒ 별도의 공통 로직으로...)
⇒ 결과적으로 core concern은 핵심 비즈니스 메소드만 딱! 남겨놓을 수 있음
⇒ 변경할 때 처리가 쉽고, 공통 로직도 원하는 대상을 골라서 쉽게 주입 가능 (보통 패키지 단위로 多)
• AOP 동작 원리 (in Spring)
• AOP 적용 전 의존 관계 / 흐름
- 컨트롤러에서 서비스 호출 시 그냥 해당 클래스와 바로 직결된 의존관계로 호출
- memberController 에서 memberService 의존 ∴ memberController의 메소드 호출 ⇒ memberService의 메소드 호출
• AOP 적용 후 의존 관계 / 흐름
- 중간에 AOP가 끼어든 상태
- AOP가 중간에서 가짜(by 프록시 기술) memberService를 만듦 (클라이언트가 요청한 메소드 가로챔)
⇒ 스프링 빈 등록 시 가짜 스프링 빈을 통해 AOP 먼저 다 실행 후 proceed()로 실제 요청한 메소드를 실행시킴
∴ 결과적으로 memberController 가 호출한 것은 가짜 memberService
- memberService를 복제해 조작한 클래스가 객체(=가짜 memberService)가 호출 됐음을 확인 가능 == 프록시가 주입됨
따라서, AOP는 DI가 기반으로 되어있기에 가능한 기술이라고도 볼 수 있다.
'Spring > Spring boot 입문' 카테고리의 다른 글
[스프링 부트] 스프링 DB 접근 기술 (6)스프링 데이터 JPA - 스프링 입문 강의 / 인프런 (0) | 2022.08.31 |
---|---|
[스프링 부트] 스프링 DB 접근 기술(5) JPA - 스프링 입문 강의 / 인프런 (0) | 2022.08.30 |
[스프링 부트] 스프링 DB 접근 기술(4)- 스프링 입문 강의 / 인프런 (0) | 2022.08.30 |
[스프링 부트] 스프링 DB 접근 기술(1)~(3)- 스프링 입문 강의 / 인프런 (1) | 2022.08.28 |
[스프링 입문 강의] H2 DB 접근 오류 - Table "MEMBER" not found (this database is empty) (0) | 2022.08.28 |