내가 기존에 나는 강의를 통해서 Spring을 계속해서 접해가고 있었다.
그렇기에 이번에는 책을 통해서 내가 몰랐던 부분을 하나하나 블로그에 적어나가려고 해당 블로그를 작성하게 됐으며,
책은 스프링 부트 핵심 가이드를 참고했으며, 부족한 부분은 부가적인 자료를 통해서 기본을 탄탄하게 쌓아나가려고 한다.
아무래도 자바를 접해본 사람이라면 누구나 AOP에 대해서는 간략하게나마 들어본 경험이 있을것이다.
그렇다면 AOP는 관점을 기준으로 묶어 개발하는 방식을 의미한다. 여기서 관점이란 어떤 기능을 구현할 때 그 기능을 '핵심 기능' 과 '부가 기능'으로 구분해 각각 하나의 관점으로 보는 것을 의미한다.
여기서 '핵심 기능'은 비지니스 로직을 구현하는 과정에서 비지니스 로직이 처리하려는 목적 기능을 말합니다.
예를 들어 클라이언트로부터 상품 정보 등록 요청을 받아 데이터베이스에 저장하고, 그 상품 정보를 조회하는 비지니스 로직을 구현한다면 아래 사항이 핵심 기능이라고 볼 수 있다.
상품 정보를 데이터베이스에 저장
저장된 상품 정보 데이터를 보여주는 코드
그렇다면 반복되는 측면이 많아진다고 했지만 실질적으로 나같은 초보 개발자에게는 그런 측면을 알기는 쉽지 않다. 그렇기에 몇가지 자료를 기반해서 대표적인 예시가 있다.
다만 여기서 Advice에 대해서 알고 있어야 한다. Advice를 알아야 좀 더 코드를 보고 이해하는 것이 가능하다.
관점의 구체적인 동작을 정희하는 부분으로 언제 어떤 동작을 수행할지를 결정한다.
@Before
: 메소드 실행 전에 동작을 수행하는 Advice@After
: 메서드 실행 후에 동작을 수행하는 Advice@AfterReturning
: 메서드가 성공적으로 반환된 후에 동작을 수행하는 Advice@AfterThrowing
: 메서드에서 예외가 발생한 후에 동작을 수행하는 Advice@Around
: 메서드 실행 전후에 동작을 수행하며, 메서드 실행을 직접 제어하는 Advice그리고 여기서는 구체적인 사례는 다루지 않는다. 다만 이런 FLOW를 가지고 있다는 점만을 참고했으면 한다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.UserService.getUser(..))")
public void logBeforeUserGet() {
System.out.println("Getting user...");
}
}
@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...");
}
}
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...");
}
}
@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
특정 경로를 의미하게 된다는 점을 감안하자!!@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가 예외처리에 사용될 수도 있다는 것만 알아두자!!
@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))
하지만 이러한 Compile-Time 방식은 가장 강력하고 정교한 AOP구현을 제공하지만, 코드 변경과 캄파일이 필요한 점이 단점이다.
해당 방식은 컴파일 이후에도 수정 없이 AOP를 적용할 수 있어 편리하지만 클래스 로더에 종속적일 수 있다.
해당 방식은 가장 우연하며, 동적인 AOP구현을 가능하게 한다. 그러나 프록시 생성 오버헤드가 발생할 수 있다.
이렇게 책을 통해서 AOP를 알면서 디테일한 부분을 짚고 넘어가면서 해당 글을 작성했다.
하지만 나는 어떻게 보면 아직 이러한 AOP를 유연하게 적용을 시킬 수는 없으리라 생각한다.
다만 AOP는 OOP와 마찬가지로 모듈화해서 재사용 가능한 구성을 만드는 것이고,
모듈화된 객체를 편하게 적용할 수 있게 함으로써 개발자가 비지니스 로직을 구현하는 데만 집중할 수 있게 도와주는 것이라는 부분은 명확하게 알고 갈 수 있어서 다행이라고 생각한다.
참고 자료
https://velog.io/@kai6666/Spring-Spring-AOP-%EA%B0%9C%EB%85%90