[Spring Boot] AOP
Updated:
AOP가 필요한 상황
✅ 모든 메소드의 호출 시간을 측정하고 싶다면?
✅ 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)
✅ 회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?
service/MemberService.java에 MemberService 회원 조회 시간 측정을 추가
join 메소드의 코드를 아래와 같이 수정
public Long join(Member member) {
long start = System.currentTimeMillis(); // millisecond로 받을 수 있음
// 로직이 끝날 때 시간을 찍어야 함
try {
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
} finally { // 예외가 생겨도 시간 출력
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("join = " + 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");
}
}
서버를 실행하고 시간이 출력 되는지 확인
⬆️ 처음 돌릴땐 오래걸리고, 두번째 실행할 땐 시간이 줄어든다.
등록도 해봤다.
❗️문제
- 회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.
- 시간을 측정하는 로직은 공통 관심 사항이다.
- 시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
- 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.
AOP 적용
AOP : Aspect Oriented Programming, 관점지향 프로그래밍 공통 관심 사항(Cross-cutting concern) vs 핵심 관심 사항(core concern) 분리
위에서는 시간측정 로직들을 메소드에 다 붙였다. ➡️ 이젠 AOP로 시간 측정 로직을 한 곳에 모으고 원하는데 적용
시간 측정 AOP 등록
src/main/java/com.example.memberproject/aop 패키지 생성 후,TimeTraceAop.java 작성
package com.example.memberproject.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component // 컴포넌트 스캔으로 등록해도 됨
// 컴포넌트 스캔보다 스프링 빈에 등록해서 쓰는것을 더 선호 -> SpringConfig에서 등록
public class TimeTraceAop { // 이 클래스를 스프링 빈으로 등록해줘야 함
@Around("execution(* com.example.memberproject..*(..))") // 어떤 메소드에 적용할 것인지 타겟팅할 수 있음
public Object execut(ProceedingJoinPoint joinPoint) throws Throwable {
// 시간 로직
long start = System.currentTimeMillis();
System.out.println("STRAT: " + joinPoint.toString()); // 어떤 메소드를 call 하는지 이름을 얻어올 수 있음
try {
// 다음 메소드로 진행
// Object result = joinPoint.proceed();// 다음 메소드로 진행
// return result;
return joinPoint.proceed(); // 인라인으로 변경
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
서버를 실행하고 결과를 확인해보자
잘 출력 되는 것을 볼 수 있다.
MemberService.java 에서 시간 측정 로직을 지우고 원래대로 돌려놓기 !
호출이 될 때마다 joinPoint의 파라미터를 사용해서 조작할 수 있다. 메서드를 호출할 때마다 인터셉트가 걸리는 것
해결
- 회원가입, 회원 조회 등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.
- 핵심 관심 사항을 깔끔하게 유지할 수 있다.
- 변경이 필요하면 이 로직만 변경하면 된다.
- 원하는 적용 대상을 선택할 수 있다.
스프링 AOP 동작 방식 설명
AOP 적용 전 의존관계
➡️ helloController가 memberService에 의존하고 있고, helloController에서 memberService의 메소드를 호출
➡️ 의존관계가 있고 그냥 호출했다.
AOP 적용 후 의존관계
➡️ AOP가 있으면 스프링은 가짜 memberService를 만든다. = 프록시
➡️ 스프링이 올라올 때, 컨테이너에 스프링 빈을 등록할 때, 진짜 스프링 빈이 아니라 가짜 스프링 빈을 앞에 세워 놓는다.
➡️ 가짜 스프링 빈이 끝나면, joinPoint.proceed() 하면 내부적으로 타고, 그때 실제 memberService를 진짜로 호출
➡️ helloController가 호출하는 것은 진짜 memberService가 아닌 프록시라는 기술로 발생하는 가짜 memberService이다.
실제 Proxy가 주입되는지 콘솔에 출력해보기
⬇️ MemberController.java의 생성자에서 찍어보면 아래와 같이 나온다.
memberService = class com.example.memberproject.service.MemberService$$EnhancerBySpringCGLIB$$7ca48fe3
보면 memberService로 끝나는 것이 아니라 \(EnhancerBySpringCGLIB\)7ca48fe3 이런게 붙어서 나온다. EnhancerBySpringCGLIB는 MemberService를 복제를 해서 코드를 조작하는 기술이다.
AOP 적용 전 전체 그림
AOP 적용 후 전체 그림
정리
H2 데이터 베이스 : 실무에서 로컬 db로 많이 쓴다. 실제 운영은 mysql 계열, jpa 를 쓰면 이런걸 바꿔치기할 수 있기 때문 jpa가 쿼리를 db에 맞춰서 sql을 만들어준다.
스프링을 어떻게 시작해야 하는지 막막했었는데, 좋은 무료 강의와 윤경님의 잘 정리된 블로그를 참고하면서 공부할 수 있었다 ㅎㅎ… 엄청 거대한 스프링… 아직 많이 모르지만,,, 열심히 해보자..!!
🟢 스프링의 흐름을 파악하자 ➡️ 전반적인 지도 그리기
🟢 각각의 기술들을 깊이있게 이해하자
🟢 스프링을 활용해서 실무에서 발생하는 문제들을 잘 해결하는 것이 훨씬 중요
🟢 핵심 원리를 이해하고, 문제가 발생했을 때, 대략 어디쯤 부터 찾아들어가면 될지, 필요한 부분을 찾아서 사용할 수 있는 능력!!
Leave a comment