꾸준히 하고싶은 개발자

AOP 본문

Spring Boot

AOP

프라우스 2023. 3. 16. 16:33

AOPAOP가 필요한 상황

모든 메소드의 호출 시간을 측정하고 싶다면?

공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?

문제

회원가입, 회원 조회에 시간을 측정하는 기능은 핵심 관심 사항이 아니다.

시간을 측정하는 로직은 공통 관심 사항이다.시간을 측정하는 로직과 핵심 비즈니스의 로직이 섞여서 유지보수가 어렵다.

시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.

시간을 측정하는 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.

  1. MemberService 에서 입력한다
package oss.board.Service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import oss.board.domain.Member;
import oss.board.repository.MemberRepository;
import oss.board.repository.MemoryMemberRepository;

import java.util.List;
import java.util.Optional;
@Transactional // 항상 JPA를 쓰라면 @Transactional annotation을  데이터 변경하거나 저장 할 때사용된다..
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    // 회원가입
    //Member member1 = result.get(); 겟으로 해서 직접 뽑아낼 수있지만 권장하지 않는다.
    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(member1 -> {
                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){
        return memberRepository.findById(memberId);

    }

}

try-finally구문쓰면 터져도찍어내고 항상 들어온다..

회원가입 시간이 78밀리세컨인줄 알수있다.

두번째하면 시간이 많이 단축된다.

다만 메소드가 많을경우가 문제인데, 메소드 전부 측정을 해야한다면, 메소드가 1000개 일 경우  start 시간, finish시간, 결과시간 을 1000번 반복해서 작성해줘야한다. ( 개발 생산성 바닥..)

공통 관심사항(cross-cutting concern) vs 핵심 관심 사항(core concern)

이것을 해결하고자 한다면, 어떠한 상황인지 살펴보아야 한다.

먼저, 회원가입, 회원 조회 등의 메소드에 시간을 측정하는 기능은 메소드의 핵심 관심 사항이 아닌, 공통 관심 사항이다.

메소드의 핵심 비즈니스 로직과 시간을측정하는 공통 관심 사항이 섞여서 유지보수가 어려우며, 시간을 측정하는 부분을 수정해야 하면 시간을 측정하는 기능을 쓰는 모든 메소드를 찾아서 고쳐줘야 한다.

하지만 시간을 측정하는 로직을 별도의 공통 로직으로 만들기가 매우 어렵다.

공통 관심사항과 핵심 관심사항을 분리하고 싶다.

이 때 쓰는것이 AOP 이다!

AOP 적용AOP: Aspect Oriented Programming

공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리

  1. 클래스를 생성한다.

2.@Aspect 어노테이션을 추가한다.

package oss.board.aop;

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class TimeTraceAop {
}

3. memberService 와같이 aop를 입력해준다.

 

package oss.board.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;

import java.awt.*;

@Aspect
//@Component // 자바 빈에 등록해줘야해서 컴포넌트 어노테이션을 써도되고
public class TimeTraceAop {
    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");//여기 안에 어떤 메서드를 콜하는 확인

        }
    }

}

4-1 컴포넌트어노테이션을 이용해서 자바빈 등록

@Component

컴포넌트 어노테이션을 사용해서 자바빈에 등록해줘도 되고

4-2Springconfig을클래스에서 빈 등록하기

5.@Around 어노테이션 쓰기

@Around:원하는 곳에 공통 관심사항 적용(타게팅)하기

package oss.board.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.awt.*;

@Aspect
@Component// 자바 빈에 등록해줘야해서 컴포넌트 어노테이션을 써도되고public class TimeTraceAop {
    @Around("execution(* oss.board..*(..))")// 패키지명  //원하는 곳에 공통 관심사항 적용(타게팅)하기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");
//여기 안에 어떤 메서드를 콜하는 확인

        }
    }
}

execuption: 실행하는

@Around("execution(* oss.board(패키지명)..*클래스(..))")// 패키지명 //원하는 곳에 공통 관심사항 적용(타게팅)하기

@Around() 어노테이션을 추가하고, excution() 메소드에 내에 패키지 설정 및 클래스 설정이 가능하다.

이렇게 원하는곳을 지정해서 편하게 볼 수 있어서, 어느 지점에서 병목현상이 일어나는지도 알 수 있다.

현재 작성한 것은 oss.board의 모든 하위 패키지의 모든 클래스에 설정이 된다.

사용 방법에 대해 좀 더 자세히 알고싶다면 매뉴얼을 참조하자.( 실무에서는 전체 기능의 5% 도 안쓴다고 하니, 필요할때 검색해서 쓰면 될것같다)

홈화면 및 회원목록을 조회했을때에 END 에 시간이 찍히는 것을 볼 수 있다

해결

회원가입, 회원 조회등 핵심 관심사항과 시간을 측정하는 공통 관심 사항을 분리한다.

시간을 측정하는 로직을 별도의 공통 로직으로 만들었다.핵심 관심 사항을 깔끔하게 유지할 수 있다.

변경이 필요하면 이 로직만 변경하면 된다.

원하는 적용 대상을 선택할 수 있다.

서비스 만 시간 만 뽑아내기

package oss.board.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.awt.*;

@Aspect
@Component// 자바 빈에 등록해줘야해서 컴포넌트 어노테이션을 써도되고public class TimeTraceAop {
    @Around("execution(* oss.board.Service..*(..))")// 패키지명 하위 서비스 나 리포지토리 등등  //원하는 곳에 공통 관심사항 적용(타게팅)하기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");//여기 안에 어떤 메서드를 콜하는 확인

        }
    }

}

결과

findMember 72 초

동작원리

AOP 적용 전 의존관계

AOP 적용 후 전체 그림

실제 Proxy가 주입되는지 콘솔에 출력해서 확인하기

'Spring Boot' 카테고리의 다른 글

스프링 핵심원리 이해 1 -예제 만들기  (0) 2023.04.08
스프링 핵심 원리  (0) 2023.04.05
스프링 DB 접근 기술  (0) 2023.03.15
스프링 빈과 의존 관계  (2) 2023.03.14
회원관리 예제 -벡엔드 개발  (0) 2023.03.12