본문 바로가기
Back-End/🌱 Spring Boot (java)

[Spring 입문] AOP 개념과 간단한 예제 - 메서드 시간측정

by 코딩하는 동현😎 2023. 1. 24.

AOP 란?

AOP(Aspect Oriented Programming)는 관점지향적 프로그래밍의 약자로 스프링 기술의 핵심이 되는 개념입니다.

관점 지향은 어떤 로직(ex. 한 기능의 총 걸린 시간 측정하기)을 기준으로 각각 모듈화 하겠다는 것입니다.

 

만약 모든 기능의 시간(회원 가입시간, 조회 시간)을 측정해서 어디부분이 성능 저하를 일으키는지 구하고 싶으면 모든 메서드에 아래와 같은 로직을 적용 시켜줘야 합니다.

public ? 측정하려는_메서드(?){
        Long start = System.currentTimeMillis();
        try {
            // 메서드의 본래 내용
        } finally {
            Long end = System.currentTimeMillis();
            Long timems = end - start;
            System.out.println("Task : "+joinPoint.toString() +" Time: "+timems+"ms");
        }
    }

 

메서드가 몇 안되면 상관없지만, 메서드 갯수가 수백개, 수천개가 넘어가면 일일히 이 코드를 적용 시켜주기 힘듭니다.

그래서 AOP에서 각 관점을 기준으로 모듈과 한다는것은 그 로직에 대한 코드를 모든 함수에 일일히 작성하지 않고, 모듈화(클래스 생성)해서 원하는함수들에게 지정을 시켜준다는 의미 입니다.

 

그래서 메서드의 부분들을 공통 관심 사항(cross-cutting concern)인지 핵심 관심 사항(core concern)인지 구분하고,

공통 관심 사항은 AOP를 적용해서 처리해주는 것입니다.


AOP 주요 개념

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한 것으로, 공통 관심 사항을 모듈화 합니다.
  • Target : Aspect를 적용하는 곳입니다. (클래스, 메서드 .. )
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것으로, 실질적인 부가기능을 담은 구현체입니다.
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능합니다. execute로 메서드 종료
  • PointCut : JointPoint의 상세한 스펙을 정의한 것으로, 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있습니다.

AOP 적용 전 원래 코드

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");
    }
}

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");
    }
}

AOP 예제 - 메서드 시간 특정하기

우선 aop 패키지를 하나 만들어주고, 안에 aop클래스를 추가했습니다.


@Aspect로 aop 파일인것을 명시하고, aop는 스프링 빈에 등록을 해야되기 때문에 @Component 어노테이션을 추가했습니다.

 

 

Aspect의 실행 시점은 아래와 같은 어노테이션으로 정의 할수 있습니다

  • @Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
  • @After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
  • @AfterReturning (정상적 반환 이후)타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
  • @AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
  • @Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행

메서드 실행 시간이므로 @Around 어노테이션을 채택했고, 그의 인자로 적용할 메서드의 범위를 지정해 줬습니다.

 

execution(* example.boot..*(..)) 가 의미하는 내용은

리턴값이 *인 함수, example.boot 패키지 안에 있는 모든 파일의 모든 메서드에 aspect를 적용한 다는 의미입니다.

 

execution(* example.boot..*.service.(..)) 가 의미하는 내용은

리턴값이 *인 함수, example.boot 패키지 안에 있는 service 패키지의 모든 메서드에 aspect를 적용한 다는 의미입니다.

 

example.boot 부분에는 여러분의 프로젝트의 패키지를 작성해주시면 됩니다.

 

aop/TimeTraceAop.java

package example.boot.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(* example.boot..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        Long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            Long end = System.currentTimeMillis();
            Long timems = end - start;
            System.out.println("Task : "+joinPoint.toString() +" Time: "+timems+"ms");
        }
    }
}

실행 결과 콘솔


별도 config 파일로 빈을 등록 했을때 순환 참조 오류

위의 예제는 @Componet 어노테이션으로 컴포넌트 스캔 방식으로 등록했지만, 만약에 별도 자바 파일로 아래와 같이 빈으로 등록했을땐, 모든 메서드에 대한 관점을 설정할때 아래의 함수도 순환참조 되는 오류가 생길수 있습니다.

 

SpringConfig.java

@Configuration
public class SpringConfig {
    @Bean
    public TimeTraceAop timeTraceAop() {
        return new TimeTraceAop();
    }
}

그러므로 아래와 같이 Configuration 파일을 제외 시켜서 순환 참조 오류를 해결 할 수 있습니다.

 

aop/TimeTraceAop.java

@Aspect
@Component
public class TimeTraceAop {
    
    //아래와 같이 수정!!!!!
    @Around("execution(* example.boot..*(..)) && !target(example.boot.SpringConfig)")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
        Long start = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            Long end = System.currentTimeMillis();
            Long timems = end - start;
            System.out.println("Task : "+joinPoint.toString() +" Time: "+timems+"ms");
        }
    }
}
반응형

댓글