Spring AOP 란?

ran·2023년 8월 27일
0

Spring

목록 보기
1/4

1. AOP란?

AOP(Aspect-Oriented Programming)는 핵심 로직과 부가 기능을 분리하여 애플리케이션 전체에 걸쳐 사용되는 부가 기능을 모듈화하여 재사용할 수 있도록 지원하는 것이다.
Aspect-Oriented Programming를 해석하면 관점(관심) 지향 프로그래밍 이 되는데, 이는 프로젝트 구조를 바라보는 관점을 바꿔보자는 의미이다.

Java에서 객체지향프로그래밍인 OOP는 목적에 따라 클래스, 객체를 만드는 방식으로, 핵심 비즈니스 로직, 부가기능 로직 등 하나의 객체로 분리하는 방식인데, 이를 관점의 시선에서 바라보는 방식이 AOP다.
사실 말로 설명하면 이해가 잘 안되니 사진 하나를 보겠다.


위의 사진에서는 핵심 기능으로 이체, 대출, 이자계산 등이 있다. 해당 서비스 로직에서 우리가 공통적으로 필요한 것은 로깅, 보안, 트랜잭션들이 있다.
이처럼 핵심 기능을 도화주는 부가적인 기능인 부가 기능을 기존 핵심기능에서 분리하고 재사용하는 것을 AOP라고 생각하면 될것이다.
위의 로깅, 보안, 트랜잭션과 같이 공통된 부분을 잘라낸것을 AOP에서 크로스 컷팅(Cross-Cutting)이라고 한다.

정리

  • OOP : 비즈니스 로직의 모듈화
    • 모듈화의 핵심 단위는 비즈니스 로직
  • AOP : 인프라 혹은 부가기능의 모듈화
    • 대표적인 예 : 모니터링 및 로깅, 동기화, 오류 검사 및 처리, 성능 최적화(캐싱) 등
    • 각각의 모듈들의 주 목적 외에 필요한 부가적인 기능들

AOP의 장점

  • 애플리케이션 전체에 흩어진 공통 기능이 하나의 장소에서 관리되어 유지보수가 좋다.
  • 핵심 로직과 부가 기능의 명확한 분리로, 핵심 로직은 자신의 목적 외에 사항들에는 신경쓰지 않는다.

2. AOP 용어 정리

  • Aspect
    • Advice + PointCut 을 모듈화 한것
    • 부가기능을 정의한 어드바이스와 어드바이스를 어디에 적용할지 결정하는 포인트 컷을 합친 개념
  • Target
    • 핵심 기능을 담고 있는 모듈로 타겟은 부가기능을 부여할 대상.
    • Advice의 대상이 되는 객체로 PointCut으로 결정된다.
  • Join Point
    • 추상적인 개념으로 Advice가 적용될 수 있는 모든 위치.
    • ex) 메서드 실행 시점, 생성자 호출 시점, 필드 값 접근 시점 등등..
    • 스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메서드 실행 시점
  • PointCut
    • 조인 포인트 중에서 advice가 적용될 위치를 선별하는 기능
    • 스프링 AOP는 프록시 기반이기 때문에 조인 포인트가 메서드 실행 시점 뿐이고, 포인트컷도 메서드 실행 시점만 가능
  • Advice
    • 타겟에 제공할 실질적인 부가 기능 로직을 정의하는 곳
    • 특정 조인 포인트에서 Aspect에 의해 취해지는 조치
  • Advisor
    • Advice + PointCut
    • Spring AOP에서만 사용되는 특별한 용어
  • Weaving
    • pointcut으로 결정한 타겟의 join point에 advice를 적용하는 것
  • AOP 프록시
    • AOP 기능을 구현하기 위해 만든 프록시 객체
    • 스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시
    • 스프링 AOP의 기본값은 CGLIB 프록시

솔직히 말장난같다...🤣 그림을 보고 이해해보자


3. Spring AOP의 프록시

Spring AOP는 기본적으로 프록시 방식으로 동작한다. 프록시 패턴이란 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조 하는 것이 아니라, 해당 객체를 대행(대리, proxy)하는 객체를 통해 대상객체에 접근하는 방식을 말한다.

그렇다면 Spring AOP는 왜 프록시 방식을 사용하는가?

프록시 객체 없이 Target 객체를 사용하고 있다고 생각해보자. Aspect 클래스에 정의된 부가 기능을 사용하기 위해서, 우리는 원하는 위치에서 직접 Aspect 클래스를 호출해야 한다. 이 경우 Target 클래스 안에 부가 기능을 호출하는 로직이 포함되기 때문에, AOP를 적용하지 않았을 때와 동일한 문제가 발생한다. 여러 곳에서 반복적으로 Aspect를 호출해야 하고, 그로 인해 유지보수성이 크게 떨어진다.

그래서 Spring에서는 Target 클래스 혹은 그의 상위 인터페이스를 상속하는 프록시 클래스를 생성하고, 프록시 클래스에서 부가 기능에 관련된 처리를 한다. 이렇게 하면 Target에서 Aspect을 알 필요 없이 순수한 비즈니스 로직에 집중할 수 있다.

아래에 예시를 들어 설명하겠다.

public interface TargetService{
    void logic();
}

@Service 
public class TargetServiceImpl implements TargetService{
    @Override 
		public void logic() {
        ...
}}

logic() 메서드가 타겟일때,

@Service 
public class TargetServiceProxy implements TargetService{ 
		// 지금은 구현체를 직접 생성했지만, 외부에서 의존성을 주입 받도록 할 수 있다.
		TargetService targetService = new TargetServiceImpl();
		...

		@Override 
		public void logic() {
        // Target 호출 이전에 처리해야하는 부가 기능
				
        // Target 호출
		targetService.logic();

        // Target 호출 이후에 처리해야하는 부가 기능
    }
}

Proxy 에서 타겟 전후에 부가 기능을 처리하고 타겟을 호출한다.

@Service 
public class UseService{ 
		// 지금은 구현체를 직접 생성했지만, 외부에서 의존성을 주입 받도록 할 수 있다.
		TargetService targetService = new TargetServiceProxy();
		...
		
		public void useLogic() {
        // Target 호출하는 것처럼 부가 기능이 추가된 Proxy를 호출한다.
				targetService.logic();
    }
}

이경우 사용자 입장에서 타겟을 사용하는 것 처럼 Proxy를 사용할 수 있다.

  • 한가지 예로 @Transactional이 AOP의 예인데, 실제 트랜잭션을 사용하려면 commit, rollback 이 있어야 되는데, @Transactional 애노테이션은 프록시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략할 수 있게하는 것이다.

4. Spring AOP 적용

메서드 실행시간 측정을 해볼것이다.

JoinPoint로 사용할 마킹용 어노테이션 작성

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExeTimer {
}

위에 작성한 어노테이션 @ExeTimer가 붙은 메서드에 대해 AOP가 수행된다.

AOP 클래스 작성(Advisor)

@Slf4j
@Aspect
@Component
public class ExecutionTimer {

    // 조인포인트를 어노테이션으로 설정
    @Pointcut("@annotation(com.aop.annotation.ExeTimer)")
    private void timer(){};

    // 메서드 실행 전,후로 시간을 공유해야 하기 때문
    @Around("timer()")
    public void AssumeExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

        StopWatch stopWatch = new StopWatch();

        stopWatch.start();
        joinPoint.proceed(); // 조인포인트의 메서드 실행
        stopWatch.stop();

        long totalTimeMillis = stopWatch.getTotalTimeMillis();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();

        log.info("실행 메서드: {}, 실행시간 = {}ms", methodName, totalTimeMillis);
    }
}
  • @PointCut을 통해 조인 포인트 중에서 advice가 적용될 위치를 지정한다.
  • 여기서는 @annotation 을 이용해서 위에서 작성한 어노테이션의 path를 지정한다.
  • 메서드 실행 전, 후로 실행시간을 측정한다.
  • Spring AOP는 @Before, @AfterReturning등 메서드 전,후로 호출되는 메서드를 지원하는데 실행시간을 측정할 때에는 한 개 변수를 실행시간 전,후로 공유해야 하기 때문에 @Around를 사용한다.
  • Stopwatch 클래스를 이용해서 시간을 측정하고, 메서드의 이름도 로그가 찍히도록한다.

이제 @ExeTimer를 메서드에 걸어주기만 하면 된다!.


참고

profile
Backend Developer

0개의 댓글