[Spring] AOP는 뭔지는 알고 쓰자!

이진영·2023년 8월 24일
0

BookTIL

목록 보기
1/2
post-thumbnail

개요

내가 기존에 나는 강의를 통해서 Spring을 계속해서 접해가고 있었다.

그렇기에 이번에는 책을 통해서 내가 몰랐던 부분을 하나하나 블로그에 적어나가려고 해당 블로그를 작성하게 됐으며,

책은 스프링 부트 핵심 가이드를 참고했으며, 부족한 부분은 부가적인 자료를 통해서 기본을 탄탄하게 쌓아나가려고 한다.


1. 관점 지향 프로그래밍(AOP)

아무래도 자바를 접해본 사람이라면 누구나 AOP에 대해서는 간략하게나마 들어본 경험이 있을것이다.

그렇다면 AOP는 관점을 기준으로 묶어 개발하는 방식을 의미한다. 여기서 관점이란 어떤 기능을 구현할 때 그 기능을 '핵심 기능' 과 '부가 기능'으로 구분해 각각 하나의 관점으로 보는 것을 의미한다.

여기서 '핵심 기능'은 비지니스 로직을 구현하는 과정에서 비지니스 로직이 처리하려는 목적 기능을 말합니다.

예를 들어 클라이언트로부터 상품 정보 등록 요청을 받아 데이터베이스에 저장하고, 그 상품 정보를 조회하는 비지니스 로직을 구현한다면 아래 사항이 핵심 기능이라고 볼 수 있다.

  1. 상품 정보를 데이터베이스에 저장

  2. 저장된 상품 정보 데이터를 보여주는 코드


그렇다면 여기서 부가 기능을 만약 넣게 된다면 모든 로직에 해당 기능을 넣게 됩니다. 그렇게 된다면 지저분해질 뿐더러 반복되는 측면이 많아지게 됩니다.

그렇다면 반복되는 측면이 많아진다고 했지만 실질적으로 나같은 초보 개발자에게는 그런 측면을 알기는 쉽지 않다. 그렇기에 몇가지 자료를 기반해서 대표적인 예시가 있다.

다만 여기서 Advice에 대해서 알고 있어야 한다. Advice를 알아야 좀 더 코드를 보고 이해하는 것이 가능하다.

관점의 구체적인 동작을 정희하는 부분으로 언제 어떤 동작을 수행할지를 결정한다.

  • @Before : 메소드 실행 전에 동작을 수행하는 Advice
  • @After : 메서드 실행 후에 동작을 수행하는 Advice
  • @AfterReturning : 메서드가 성공적으로 반환된 후에 동작을 수행하는 Advice
  • @AfterThrowing : 메서드에서 예외가 발생한 후에 동작을 수행하는 Advice
  • @Around : 메서드 실행 전후에 동작을 수행하며, 메서드 실행을 직접 제어하는 Advice

그리고 여기서는 구체적인 사례는 다루지 않는다. 다만 이런 FLOW를 가지고 있다는 점만을 참고했으면 한다.

  1. 로깅(Logging): 메서드의 호출과 반환 값을 로깅하는 작업은 많은 애플리케이션에서 중요합니다. AOP를 사용하면 모든 메서드 호출에 대한 로그를 중앙에서 관리할 수 있습니다.
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.UserService.getUser(..))")
    public void logBeforeUserGet() {
        System.out.println("Getting user...");
    }
}

  1. 트랜잭션 관리(Transaction Management): 데이터베이스 트랜잭션을 관리할 때 AOP를 사용하여 트랜잭션의 시작과 종료, 롤백 등을 처리할 수 있습니다. 이를 통해 코드 중복을 줄이고 트랜잭션 코드를 분리하여 관리할 수 있습니다.
@Aspect
@Component
public class TransactionAspect {

    @AfterReturning("execution(* com.example.service.ProductService.*(..))")
    public void commitTransaction() {
        System.out.println("Committing transaction...");
    }

    @AfterThrowing("execution(* com.example.service.ProductService.*(..))")
    public void rollbackTransaction() {
        System.out.println("Rolling back transaction due to exception...");
    }
}

  1. 보안(Security): 인증과 권한 부여와 같은 보안 관련 작업은 애플리케이션의 여러 부분에서 발생할 수 있습니다. AOP를 이용하여 이러한 보안 작업을 모듈화하고 중앙에서 관리할 수 있습니다.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SecurityAspect {

    @Before("execution(* com.example.controller.AdminController.*(..))")
    public void checkAdminPermission() {
        System.out.println("Checking admin permission...");
    }
}

  1. 캐싱(Caching): 메서드 결과를 캐시하여 성능을 향상시키는 작업도 AOP를 통해 처리할 수 있습니다. 특정 메서드의 결과를 캐시하고 캐시 유효성을 관리하는 코드를 분리할 수 있습니다.
@Aspect
@Component
public class CachingAspect {

    @AfterReturning(pointcut = "execution(* com.example.service.CacheService.*(..))", returning = "result")
    public void cacheMethodResult(Object result) {
        // Cache the result...
        System.out.println("Caching method result...");
    }
}
  • 여기서 pointcut 특정 경로를 의미하게 된다는 점을 감안하자!!

  1. 예외 처리(Exception Handling): 예외 처리 로직도 AOP로 처리할 수 있습니다. 예를 들어 특정 예외가 발생하면 특정 로그를 남기거나, 예외를 특정한 방식으로 처리하는 등의 작업을 AOP로 모듈화할 수 있습니다.
@Aspect
@Component
public class ExceptionLoggingAspect {

    @AfterThrowing(pointcut = "execution(* com.example.service.PaymentService.*(..))", throwing = "exception")
    public void logException(Exception exception) {
        System.out.println("Exception caught: " + exception.getMessage());
    }
}

하지만 이러한 예외처리에 있어서는 간편한 사례들이 있다 다만 여기서는 AOP가 예외처리에 사용될 수도 있다는 것만 알아두자!!


  1. 성능 모니터링(Performance Monitoring): 애플리케이션의 성능을 모니터링하기 위해 AOP를 사용하여 메서드 실행 시간을 측정하거나, 리소스 사용량을 추적하는 등의 작업을 수행할 수 있습니다.
@Aspect
@Component
public class PerformanceMonitoringAspect {

    @Around("execution(* com.example.service.AnalyticsService.*(..))")
    public Object measureExecutionTime(org.aspectj.lang.ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("Method execution time: " + (endTime - startTime) + "ms");
        return result;
    }
}

이러한 대표적인 부가 기능이 추가 됐을 때는 AOP를 통해서 문제를 해결해 나갈 수가 있다.

또한 부가적으로 지금까지는 경로를 통해서 어떤 특정 메소드를 명시했지만 Bean에 이름이나 @특정 어노테이션을 명시할 수도 있다.

@Before(bean(service name))
@Before(@annotation(annotation name))

그렇다면 이러한 부가기능을 AOP로 처리했을 때의 장점은?

  • 유지보수
  • 상황에 맞는 AOP적용이 좀 더 정확히 수행될 가능성이 크다!!

그리고 적용 방식 또한 다양하다.

  1. 컴파일 타임(Compile-Time) 방식

    컴파일 타임에서 AOP적용이 이루어지는 방식을 의미하며 AspectJ와 같은 AOP 프레임워크를 사용하여 코드를 컴파일할 때 관점이 적용된다.

하지만 이러한 Compile-Time 방식은 가장 강력하고 정교한 AOP구현을 제공하지만, 코드 변경과 캄파일이 필요한 점이 단점이다.

  1. 로드 타임(Load-Time) 방식

    로드 타임에서 AOP가 이루어지는 방식이다. 클래스 로더가 클래스를 로드하는 시점에 관점을 적용합니다. 즉 해당 말은 클래스 로더가 클래스를 로드할 때 바이트 코드를 수정하여 관점을 삽입

해당 방식은 컴파일 이후에도 수정 없이 AOP를 적용할 수 있어 편리하지만 클래스 로더에 종속적일 수 있다.

  1. 런타임(Runtime) 방식

    런타임에서 AOP적용이 이루어진다. Spring AOP와 같은 프록시 기반 AOP프레임워크를 사용하여 런타임 중에 프록시 객체를 생성하여 관점을 적용합니다.

해당 방식은 가장 우연하며, 동적인 AOP구현을 가능하게 한다. 그러나 프록시 생성 오버헤드가 발생할 수 있다.

마치면서

이렇게 책을 통해서 AOP를 알면서 디테일한 부분을 짚고 넘어가면서 해당 글을 작성했다.

하지만 나는 어떻게 보면 아직 이러한 AOP를 유연하게 적용을 시킬 수는 없으리라 생각한다.

다만 AOP는 OOP와 마찬가지로 모듈화해서 재사용 가능한 구성을 만드는 것이고,

모듈화된 객체를 편하게 적용할 수 있게 함으로써 개발자가 비지니스 로직을 구현하는 데만 집중할 수 있게 도와주는 것이라는 부분은 명확하게 알고 갈 수 있어서 다행이라고 생각한다.

참고 자료
https://velog.io/@kai6666/Spring-Spring-AOP-%EA%B0%9C%EB%85%90

https://code-lab1.tistory.com/193

https://engkimbs.tistory.com/746

profile
내가 공부한 것들을 적는 공간

0개의 댓글