스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
인프런 김영한님 강좌인 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 내용을 학습하며 다룬 글입니다.
문제(저작권, 오류 및 잘못된 부분 등)가 있다면 알려주세요!!
PREV)
스프링 입문 - 6. 스프링 DB 접근 기술
[1. AOP가 필요한 상황]
[2. AOP 적용]
AOP (Aspect Oriented Programming) 은 공통된 로직이나 기능을 모듈화하는 것이다.
핵심 관심 사항 (core concern) : 회원 가입과 같은 비지니스 로직
공통 관심 사항(cross-cutting concern) : 그 외의 부가 기능 로직
OOP ( 객체 지향 프로그래밍, Object Oriented Programming) 에서 좀 더 나아가 AOP를 적용하면서 핵심 관심 사항과 공통 관심 사항을 분리하며, 공통된 부분을 재사용할 수 있도록 하고, 비지니즈에 집중하기 쉽도록 한다.
[1. AOP가 필요한 상황]
모든 메서드의 호출 시간을 측정하고 싶을 때,
위의 사진 처럼 각 시간 측정 로직을 추가하여야 한다.
MemberService 회원 조회 시간 측정 추가
java/hello/hellospring/service/MemberService.java
/*
회원 가입
*/
public Long join(Member member) {
long start = System.currentTimeMillis();
try { //같은 이름인 중복 회원 X
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");
}
}
- System.currentTimeMillis()를 이용하여 시작과 끝의 시간을 측정하며 로직이 실행되는 시간을 구한다.
[실행 결과]
문제점)
이런 시간 측정 기능은 핵심 관심 사항이 아닌, 공통 관심 사항이다.
핵심 비지니스 로직과 섞이게 되면 유지보수가 힘들 뿐더러, 별도의 공통 로직으로 만들기 매우 어렵다.
또한, 변경 사항이 있을 경우에 모든 로직을 찾아가며 변경해야 하므로 번거롭다는 다양한 문제점들이 있다.
따라서 이러한 상황에서 AOP를 적용한다.
[2. AOP 적용]
AOP: Aspect Oriented Programming
공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리
기존의 memberService에는 핵심 관심 사항만 남기고 다음을 진행한다. (시간 측정 로직 삭제)
시간 측정 AOP 등록
java/hello/hellospring/aop/TimeTraceAop.java
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 // AOP 적용에 필요
@Component // 로 스프링 빈으로 등록해주어도 되지만, SpringConfig에 등록하여 사용하는 것도 좋음.
public class TimeTraceAop {
@Around("execution(* hello.hellospring..*(..))") // AOP를 어디에 적용해줄 지 타켓을 지정한다.
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");
}
}
}
- @Aspect : AOP 적용에 필요한 어노테이션, component를 포함하고 있는 것이 아니므로 스프링 빈 등록 과정이 필요하다.
- @Around("excution ~") : AOP 적용 대상을 설정해준다.
+) 스프링 빈으로 등록하는 경우 (SpringConfig)
@Bean
public TimeTraceAop timeTraceAop() {
return new TimeTraceAop();
}
[실행 결과]
메서드 호출 때마다 실행 시간 측정이 잘 진행된다.
해결)
시간 측정 로직을 별도의 공통 로직으로 만들며 핵심 관심 사항과 공통 관심 사항을 분리할 수 있다.
따라서 핵심 관심 사항에 집중하는 데에 방해되지 않도록 한다.
변경이 필요한 경우 해당 공통 관심 사항 로직만 변경하면 된다.
원하는 대상을 선택하여 적용시킬 수 있다.
AOP 적용 전 의존관계
AOP 적용 후 의존관계
- AOP를 적용하게 되면, 적용 전과 다르게 실제 memberService와 연결되지 않고 가짜 memberService가 연결된다.
- 즉, 프록시 memberService가 먼저 호출되고, joinPoint.proceed()가 먼저 실행된 후 실제 memberService의 메서드가 실행된다.
AOP 적용 전 전체 그림
AOP 적용 후 전체 그림
프록시 확인
@Autowired
public MemberController(MemberService memberService){
this.memberService = memberService;
System.out.println("memberService = " + memberService.getClass());
}
MemberService 이후로 $$~가 붙어있는데 이는 프록시 memberService를 주입한 것이다.