용어 | 설명 |
---|---|
Aspect | 부가기능(Advice)을 모아놓은 클래스 |
Advice | Aspect에서 실제로 수행할 동작을 정의한 메서드(execute() ) |
JoinPoint | Advice가 적용될 수 있는 실행 지점 메서드 실행, 객체 생성 등 다양한 지점이 될 수 있음 |
Pointcut | Advice가 적용될 메서드나 클래스의 범위를 지정하는 표현식"execution(* hello.hello-spring..*(..))" 특정 패키지의 모드 메서드 지정 |
⏰ 만약 각 메서드의 실행 시간을 측정하고 싶다면? - 시간 측정 로직을 모두 추가
📂 MemberService
모든 메서드에 시간 측정 로직을 추가
@Transactional
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
// 회원가입 - 동일인의 회원 중복 가입❌
public Long join(Member member){
long start = System.currentTimeMillis();
try {
validateDuplicatedMember(member);
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("join() = " + timeMs + "ms");
}
}
// 중복 회원 확인
private void validateDuplicatedMember(Member member) {
long start = System.currentTimeMillis();
try {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("validateDuplicatedMember() = " + 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");
}
}
// 단일 회원 조회
public Optional<Member> findMember(Long memberId){
long start = System.currentTimeMillis();
try {
return memberRepository.findById(memberId);
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMember() = " + timeMs + "ms");
}
}
}
validateDuplicatedMember() = 4ms
join() = 7ms
findMembers() = 11ms
@Around
📂 TimeTraceAop
@Aspect
@Component // 또는 SpringConfig에 @Bean으로 등록(선호하는 방법)
public class TimeTraceAop {
@Around("execution(* hello.hello_spring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
long start = System.currentTimeMillis();
// joinPoint.toString() : 어떤 메서드를 호출하는지 얻어옴
System.out.println("START: " + joinPoint.toString());
try {
Object result = joinPoint.proceed();
return result;
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
START: execution(String hello.hello_spring.controller.MemberController.list(Model))
START: execution(List hello.hello_spring.service.MemberService.findMembers())
START: execution(List org.springframework.data.repository.ListCrudRepository.findAll())
Hibernate: select m1_0.id,m1_0.name from member m1_0
END: execution(List org.springframework.data.repository.ListCrudRepository.findAll()) 4ms
END: execution(List hello.hello_spring.service.MemberService.findMembers()) 4ms
END: execution(String hello.hello_spring.controller.MemberController.list(Model)) 9ms
@Around("execution(* hello.hello_spring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
@Around
: 해당 메서드가 지정된 포인트컷의 전후에 실행될 Advice임을 명시"execution(* hello.hello_spring..*(..))"
: hello.hello_spring
패키지 이하의 모든 메서드에 적용하겠다는 의미(join point)Object result = joinPoint.proceed();
return result;
proceed()
는 프록시된 대상 메서드를 실행하며, 예외를 던질 수 있어 throws Throwable이 필요어노테이션 | 설명 |
---|---|
@Aspect | 해당 클래스가 AOP의 관심사(Aspect)를 정의하는 클래스임을 명시 |
@Before | 핵심 로직 실행 전 실행될 Advice 정의 |
@After | 핵심 로직 실행 후 실행될 Advice 정의 |
@AfterReturning | 핵심 로직이 정상적으로 반환된 후 실행될 Advice 정의 |
@AfterThrowing | 예외가 발생했을 때 실행될 Advice 정의 |
@Around | 핵심 로직의 전후를 모두 감싸는 Advice (가장 강력한 형태) |
어노테이션 | 설명 |
---|---|
@EnableAspectJAutoProxy | AOP 프록시를 활성화하는 어노테이션 보통 @Configuration 클래스에 선언 |
@Component | @Aspect 클래스에 필수 스프링 빈으로 등록되기 위해 필요 |
joinPoint.proceed()
를 실행하면 내부적으로 진짜 스프링 빈을 호출memberController
가 호출하는 건 프록시 memberService
를 호출하는 것 @Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
System.out.println("memberService = " + memberService.getClass());
}
memberService = class hello.hello_spring.service.MemberService$$SpringCGLIB$$0