본 게시글은 김영한님의 [스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술]을 수강하며 작성한 글입니다.
1. AOP가 필요한 상황
1) AOP가 필요한 상황
- 모든 메서드의 호출 시간을 측정하고 싶다면?
- 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)
- 회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?
2) MemberService에 시간 측정하는 로직 추가
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@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{
validateDuplicateMember(member); // 중복 회원 검증
memberRepository.save(member);
return member.getId();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("join = " + timeMs + "ms");
}
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/**
* 전체 회원 조회
*/
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> findOne(Long memberId) {
long start = System.currentTimeMillis();
try {
return memberRepository.findById(memberId);
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("findMembers = " + timeMs + "ms");
}
}
}
💡 회원가입, 회원 조회의 시간을 측정하는 로직은 핵심 관심 사항이 아니라 공통 관심 사항이다.
- 위와 같은 방식의 문제점
- 시간을 측정하는 로직과 핵심 비즈니스 로직이 섞여서 유지보수가 어렵다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.
- 시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.
2. AOP 적용
1) AOP란
- AOP: Aspect Oriented Programming
- 공통 관심 사항과 핵심 관심 사항을 분리하는 기술이다. 따라서 시간 측정 로직을 따로 만들고, 이를 원하는 곳에 적용시키면 된다.
2) AOP 적용하기
1. hellospring 하위에 aop 패키지를 생성하고 TimeTraceAop 클래스를 생성한다.
package hello.hellospring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class TimeTraceAop {
public Object excute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
💡 AOP는 @Aspect 어노테이션을 붙여주어야 한다.
2. TimeTraceAop를 스프링 빈으로 등록한다.
- SpringConfig를 통해 등록하는 방법
package hello.hellospring;
import hello.hellospring.aop.TimeTraceAop;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
};
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
}
- @Component 어노테이션을 이용하는 방법
package hello.hellospring.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
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
@Around 어노테이션을 이용하여 해당 클래스를 실행할 범위를 지정한다.
3) AOP 적용의 이점
- 회원가입, 회원 조회 등의 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.
- 시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.
- 핵심 관심 사항을 깔끔하게 유지할 수 있다.
- 변경이 필요하면 이 로직만 변경하면 된다.
- 원하는 적용 대상을 선택할 수 있다.
4) AOP 동작 방식
- AOP 적용 전 의존관계
Controller가 Service를 바로 호출한다.
- AOP 적용 후 의존관계
Controller는 **가짜 Service(프록시)**를 호출하게 된다.
'Courses > Spring' 카테고리의 다른 글
[스프링 핵심 원리 - 기본편] 2. 스프링 핵심 원리 이해 1 - 예제 만들기 (0) | 2023.10.16 |
---|---|
[스프링 핵심 원리 - 기본편] 1. 객체 지향 설계와 스프링 (0) | 2023.10.06 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 6. 스프링 DB 접근 기술 (0) | 2023.09.24 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 5. 회원 관리 예제 MVC 개발 (0) | 2023.09.24 |
[스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술] 4. 스프링 빈과 의존관계 (0) | 2023.09.21 |