Spring/Spring boot 입문

[스프링 부트] AOP - 스프링 입문 강의 / 인프런

mopipi 2022. 9. 3. 01:32
반응형

6. AOP

더보기

1. AOP가 필요한 상황

2. AOP 적용


1) AOP가 필요한 상황

AOP의 본격적인 이해에 앞서, 상황을 예로들어 어떤 경우에 AOP가 사용 되는지 알아보자! 


어떤 경우에 AOP를 사용하나?

  1.  모든 메서드의 호출 시간 측정
  2.  공통 관심 사항 (cross-cutting concern) vs. 핵심 관심 사항 (core concern)
  3.  회원 가입 시간, 회원 조회 시간 측정

 

  회원 조회(findMembers), 가입(join) 시간 측정 추가 ( in 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"); //메서드 명 + 걸린 시간 출력
    }

}

- 기존의 join 메서드를 약간 수정해줬다. 

- 원리는 로직 끝나는 시간 - 로직 시작 시간으로 간단하게 계산하는 방식

  ✔ 이때 시간 찍기위해 >> System.currentTimeMillis() 사용 [ms 단위로 시간 측정해줌]

- 로직 끝나는 시간은 try가 끝난 이후 finally 문에서 실행해준다.

 

테스트 코드 실행 결과 join시 3ms가 걸리는 것을 알 수 있다.

➤ 마찬가지로 findMembers에도 똑같은 짓 반복해야 함^^;

public List<Member> findMembers(){

    long start = System.currentTimeMillis(); //로직 시작 시간 찍기
    try {
        return memberRepository.findAll(); //List형으로 반환
    }finally {
        long finish = System.currentTimeMillis(); //로직 끝나는 시간 찍기
        long timeMs = finish - start; //로직 돌아가는데 걸린 시간 계산
        System.out.println("findMembers = " + timeMs + "ms"); //메서드 명 + 걸린 시간 출력
    }
}

 

출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)

➤ 클래스마다 시간 측정 로직을 하나하나 붙여줌 → 문제 상황 발생

문제상황
 1, join, findMembers가 try문 안에 들어가고 부가 기능인 시간 측정이 더 눈에 띄는 주객전도 상황... 핵심 관심 사항 X
 2. 시간 측정 로직은 just 공통 관심 사항
 3. But 공통 관심 사항 + 핵심 관심 사항(=핵심 비즈니스 로직)이 섞여서 혼란 ... 유지 보수 당연 어렵지
 4. 그렇다고 시간 측정 로직을 공통으로 빼? 불가능...
 5. 만약 시간 측정 로직을 수정하고 픔 >> 전부 찾아가서 변경^^ 

 

 ... ಠ_ಠ 이걸 계속 하라고?... 장난하쉼?... ➤➤➤ 그래서 등장했다 AOP !!!


2) AOP의 적용

 

문제 상황이 어떤지 알았으니, 이제 어떻게 쓰여서/ 어떻게 작동하는지 알아보자!


배경) 비즈니스 컴포넌트 개발시 중요한 원칙

 

 1) 낮은 결합도 : DI(의존성 주입)을 통해 컴포넌트 구성 객체들의 결합도 낮출 수 있음 ⇒ 의존관계 변환 easy

 2) 높은 응집도 : 핵심 비즈니스 로직을 분리하여 유지보수에 용이하게 하고, 부가적이고 반복적으로 등장하는 코드들은         따로 몰아서 효율적으로 관리하자 AOP 담당 

출처: https://heidish.tistory.com/72?category=889082

AOP

-  정의 : Aspect Oriented Programming [관점 지향 프로그래밍]

    ⇒ 공통 관심 사항 /// 핵심 관심 사항으로 분리하는 것  ➤ "관심 분리"

-  즉 앞서 나온 주객전도 상황을 방지해주는 것이 바로 AOP가 하는 일

-  공통 관심 사항을 한 곳에 다 모아서 구현한 후, 원하는 곳에 적용하는 것! 

출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)

요런 개념

 

• 시간 측정 로직의 AOP 구현

  1. AOP 패키지 생성 (hellospring 하위에) >> 그 아래 TimeTraceAop 클래스 생성

 

  2. AOP 구현

  • AOP로 쓰기 위해선 @Aspect Annotation 필수!
@Aspect
public class TimeTraceAop {

    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        //구현 로직은 전과 동일
        long start = System.currentTimeMillis();
        //어떤 메서드를 call 하는지 확인하기
        System.out.println("START: " + joinPoint.toString());
        try {
            //proceed = 다음 메서드로 진행시킴
            Object result = joinPoint.proceed();
            return result;

        }finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            //메서드 이름 +
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

 

♘ joinPoint
 - 메서드가 호출될 때마다 joinpoint로 정보 날라감
 - 호출시 마다 중간에서 intercept가 발생함 → 중간에 인터셉트해서 처리해주는 기술이 AOP임
 - 제공하는 메소드 中 getSignature() : 호출되는 메소드에 대한 다양한 정보를 Signature 객체로 넘김
 - 과정) 클라이언트가 비즈니스 메소드 호출 → 스프링 컨테이너가 JoinPoint 객체를 생성 → 메소드 호출 관련 모든 정보를 객체에 저장 → execute 호출 시 인자로 넘김


♘ ProceedingJoinPoint (interface)
 - JoinPoint를 상속받은 클래스에 proceed() 메소드가 더해진 것
 - 클라이언트의 요청을 가로챘으니 클라이언트가 호출한 비즈니스 메소드를 호출해 줘야함
 - proceed() 메소드를 통해 비즈니스 메소드 호출 가능

  3. 작성한 TimeTraceAop를 스프링 빈으로 등록 ⇒ Annotation 이용 / 스프링 빈에 직접 등록

   

   1) @Component Annotation 이용 (Component Scan)

import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimeTraceAop {

    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    ...

   2) 스프링 빈에 직접 등록 (SpringConfig) >> 사용 多  (∵ 특별한 경우이므로)

//SpringConfig
/**
 * TimeTraceAop 스프링 빈으로 직접 등록
 */
@Bean
public TimeTraceAop timeTraceAop() {
    return new TimeTraceAop();
}

 

4. TimeTraceAop 클래스 안에 @Around Annotation 추가

    (➤ 공통 관심 사항을 어디에 적용해 줄지 타겟팅 해줌! )

@Around("execution(* 패키지명..*(..))") 
  - 패키지 하위에 전부 적용함
  - 경우에 따라 원하는 클래스명만 정의 가능

@Component 
 - 스프링 컨테이너가 작동하며 클래스를 스캔해서, @Component Annotation이 붙은 클래스들은 자동으로 객체 생성해 스프링 빈에 올림
@Aspect
@Component
public class TimeTraceAop {
    
    @Around("execution((* practice.spring.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    ...

 

5. 서버를 돌려보자

시작부터 스프링 빈 자체가 올라가며 start, end 와 걸린 시간이 로그로 찍한다.

웹 브라우저를 띄운 후, 회원 목록을 클릭하자

회원 목록 조회와 관련된 요소들(Controller, Service, Repository)이 순서대로 전부 실행되고, 각 소요시간이 출력됨을 알 수 있다.  (중간에 직접 작성한 시간 측정 코드가 들어가있다)

 


+)  Advice 

  • 공통으로 등장해 AOP로 분리한 코드(=공통 관심 사항)에 해당
  • 5가지 동작 시점이 존재 : before, after, after-returning, after-throwing, around
    • before : 비즈니스 메소드 실행 전 동작
    • After - Returning : 비즈니스 메소드 성공적 리턴 후 동작 (메소드 내부에 리턴값 존재해야 함)
    • After - Throwing : 비즈니스 메소드 실행 중 실패 (예외 발생)시 동작 (catch부분)
    • After : 비즈니스 메소드 실행 후 무조건 실행 (finally)
    • Around : 메소드 호출 자체를 가로채서(intercept) 비즈니스 메소드 특정 실행 시점에 로직 삽입 가능

•  AOP가 하는 일_최종_최최최종

- 예시에서 볼 수 있듯, 회원 가입/회원 조회... 핵심 관심 사항(core concern) ⇔ 공통 관심 사항(cross cutting concern)   

   을 분리 해준다. (시간 경과 측정 로직 ⇒ 별도의 공통 로직으로...)

⇒ 결과적으로 core concern은 핵심 비즈니스 메소드만 딱! 남겨놓을 수 있음

⇒ 변경할 때 처리가 쉽고, 공통 로직도 원하는 대상을 골라서 쉽게 주입 가능 (보통 패키지 단위로 多)

 

•  AOP 동작 원리 (in Spring)

   • AOP 적용 전 의존 관계 / 흐름

 

출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)

 

memberController.class

    - 컨트롤러에서 서비스 호출 시 그냥 해당 클래스와 바로 직결된 의존관계로 호출 

    - memberController 에서 memberService 의존 ∴ memberController의 메소드 호출 ⇒ memberService의 메소드 호출

 

   • AOP 적용 후 의존 관계 / 흐름 

출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)

    - 중간에 AOP가 끼어든 상태

    - AOP가 중간에서 가짜(by 프록시 기술) memberService를 만듦 (클라이언트가 요청한 메소드 가로챔) 

      ⇒  스프링 빈 등록 시 가짜 스프링 빈을 통해 AOP 먼저 다 실행 후 proceed()로 실제 요청한 메소드를 실행시킴

      ∴ 결과적으로 memberController 가 호출한 것은 가짜 memberService

실제로 getClass를 이용해 확인한 결과

    - memberService를 복제해 조작한 클래스가 객체(=가짜 memberService)가 호출 됐음을 확인 가능 == 프록시가 주입됨

    

따라서, AOP DI가 기반으로 되어있기에 가능한 기술이라고도 볼 수 있다. 

 

반응형