공통 코드 (공통 관심사)를 비즈니스 로직과 분리하여, 실제 로직이 실행되기 전후로 공통 관심사를 실행
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");
}
}
참고) 초기 실행 시간은 Class metadata 로딩 등으로 오래 걸릴 수 있다. 따라서 실제 운영에선 서버 올리고 이것저것 호출하는 warm up을 수행한다.
: 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리
시간 측정 로직을 메소드 별로 붙이는 게 아니라, 한 곳에 모아 원하는 곳에 적용하는 것
시간 측정 AOP 등록
@Aspect
@Component
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println(joinPoint.toString());
try {
// 다음 메소드로 진행
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println(joinPoint.toString() + " " timeMs + "ms");
}
}
}
@Aspect
공통 기능을 제공하는 Aspect 클래스를 작성한다.
@Component
AOP 클래스를 빈으로 등록한다.
SpringConfig.java에서 빈으로 등록 가능
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
→ 직접 @Bean으로 등록했을 때 순환 참조가 발생하는 이유
: TimeTraceAop의 AOP 대상을 지정하는 @Around 코드(@Around("execution(* hello.firstspring..*(..))")
)에서 SpringConfig의 timeTraceAop() 메서드도 AOP 대상으로 처리하기 때문이다. 이게 바로 자기 자신인 TimeTraceAop를 생성하는 코드인지라 순환 참조 문제가 발생한다.
반면 컴포넌트 스캔을 사용할 때는 AOP의 대상이 되는 이런 코드 자체가 없기 때문에 문제가 발생하지 않는다.
AOP 설정 클래스를 빈으로 직접 등록할 때는 아래와 같이 AOP 대상에서 SpringConfig를 빼주면 된다.
@Aspect
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..)) && !target(hello.hellospring.SpringConfig)")
//@Around("execution(* hello.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {...}
}
@Around()
공통 관심 사항을 어디까지 적용할 건지 타겟팅한다.
AOP 적용 전 의존관계
AOP 적용 전에는 Controller가 Service를 의존하여 호출
AOP 적용 후 의존관계
AOP를 적용하여 적용 범위를 지정하면 Spring이 프록시라는 가짜 Service를 생성
→ Container에 Spring bean을 등록할 때 진짜 Spring bean이 아닌 프록시 Spring bean을 등록
→ 프록시 Spring bean에서 joinPoint.proceed()
호출 시 실제 Service를 호출
AOP 적용 후 전체 그림
Container에서 스프링 빈을 관리하여, 실제 대신 Proxy를 만들어 주입을 해주면 되니 DI의 장점 → AOP의 기반이 된다.