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");
}
}
ms로 시간을 조회하는 코드를 추가해보았다.
지금은 join 메소드 하나에만 추가를 했지만 모든 메소드의 호출 시간을 알기 위해서는 하나하나 메소드마다 코드를 작성해야 한다.
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member);
return member.getId();
}
이 메소드의 핵심 기능은 위와 같다. 하지만 시간을 측정하는 기능을 넣어서 굉장히 관리가 어려워 보인다.
위와 같았던 구조가
이처럼 바뀐 것이다!
< TimeTraceAop >
// 스프링 빈에 등록 -> SpringConfig에 timeTraceAop
// @Component으로 스프링 빈에 등록해도 됨
@Aspect // AOP 로 쓰기 위해 등록
@Component
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..))") // hellospring 패키지 하위에 전부 적용
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
// 변수 인라인 화 (Ctrl + Shift + Alt + T)
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() +" " + timeMs + "ms");
}
}
}
TimeTraceAop를 만들었으니 이제 MemberService의 join 메소드 안에 복잡하게 구현했던 것들은 지워도 된다.
위의 TimeTraceAop는 @Component를 이용해서 스프링 빈에 등록하였는데,
@Bean // TimeTraceAop를 스프링 빈에 등록
public TimeTraceAop timeTraceAop()
{
return new TimeTraceAop();
}
SpringConfig에서 위처럼 등록할 수도 있다.
@Around("execution(* hello.hellospring..*(..))") // hellospring 패키지 하위에 전부 적용
TimeTraceAop의 AOP 대상을 지정하는 @Around 코드를 보면 SpringConfig의 timeTraceAop() 메서드도 AOP로 처리를 하게 된다.
그런데 여기서 이 timeTraceAop()는 바로 자기 자신인 TimeTraceAop를 생성하는 코드이다. 따라서 순환참조 문제가 발생한다.
@Component 스캔을 사용할 때는 AOP의 대상이 되는 이런 코드(timeTraceAop 같은) 자체가 없기 때문에 문제가 발생하지 않은 것이다.
따라서 AOP 설정 클래스를 직접 빈으로 등록할 때는 아래처럼 AOP 대상에서 SpringConfig를 빼주면 된다.
@Around("execution(* hello.hellospring..*(..)) && !target(hello.hellospring.SpringConfig)")
//@Around("execution(* hello.hellospring..*(..))") // hellospring 패키지 하위에 전부 적용
원하는 적용 대상을 선택하는 코드는
@Around("execution(* hello.hellospring..*(..))") // hellospring 패키지 하위에 전부 적용
이 부분인데, 지금은 전부 적용하였지만 응용하여 사용하면 된다.
< AOP 적용 전 의존관계 >
< AOP 적용 후 의존관계 >
스프링은 가짜 memberService를 만든다. 이를 프록시라고 한다.
스프링 컨테이너에 스프링 빈을 등록할 때 진짜 스프링 빈이 아닌 가짜 스프링 빈을 앞세운다.
가짜 스프링 빈이 joinPoint.proceed()를 끝나면 그때 진짜 스프링 빈이 호출된다.
즉, memberController가 호출하는 것은 가짜 memberService 인 것이다.
< AOP 적용 전 전체그림 >
< AOP 적용 후 전체그림 >
AOP 타겟을 전부 다 설정했기 때문에 전체 그림이 위처럼 된다.
직접 확인해보자!!
< MemberController >
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
System.out.println("memberService = " + memberService.getClass()); // 프록시 조회해보기
}
memberService = class hello.hellospring.service.MemberService$$EnhancerBySpringCGLIB$$7ce27c0f
MemberService 뒤에 $$로 뭐가 막 붙어있다! 바로 프록시다!!
간단히 말하면 MemberService를 복제해서 조작한 기술이다.
이게 다 AOP가 적용되어 일어나는 일임을 명심하자!