2024. 5. 18. 18:09ㆍBack-End/Spring
애플리케이션 개발에서는 종종 로깅, 보안, 트랜잭션 관리와 같은 공통된 기능을 여러 모듈에서 사용해야 할 때가 있다. 이러한 기능을 각 모듈에 직접 코딩하면 코드의 중복이 발생하고 유지보수가 어려워진다. 이를 해결하기 위해 등장한 개념이 AOP(Aspect-Oriented Programming)이다.
AOP란?
AOP는 관점 지향 프로그래밍(Aspect-Oriented Programming)의 약자로, 프로그램의 주요 비즈니스 로직과는 별개로 존재하는 부가적인 기능(Aspect)을 모듈화하여 관리하는 프로그래밍 패러다임이다. 핵심 비즈니스 로직을 침해하지 않으면서, 횡단 관심사(Cross-Cutting Concerns)를 분리하여 코드의 재사용성과 유지보수성을 향상시킨다.
AOP의 주요 개념
- Aspect: 횡단 관심사를 모듈화한 것. 예를 들어, 로깅이나 트랜잭션 관리가 Aspect가 될 수 있다.
- Join Point: Aspect가 적용될 수 있는 애플리케이션의 특정 지점. 메서드 호출, 객체 생성 등이 Join Point가 될 수 있다.
- Advice: 특정 Join Point에서 수행될 동작을 정의한 것. 언제 실행될지를 정의할 수 있다.
- Pointcut: 특정 Join Point를 선택하는 표현식. Aspect가 어디에 적용될지를 정의.
- Introduction: 기존 클래스에 새로운 메서드나 필드를 추가하는 것.
- Weaving: Aspect와 다른 객체를 연결하여, Advice가 적절한 Join Point에서 실행되도록 하는 과정.
스프링에서 AOP 사용하기
스프링은 @AspectJ 애노테이션 스타일과 XML 설정 스타일, 두 가지 방식으로 AOP를 지원한다. 여기서는 @AspectJ 애노테이션 스타일을 사용하여 AOP를 적용하는 방법을 살펴보겠다.
1. 의존성 추가
스프링 AOP를 사용하려면 spring-boot-starter-aop 의존성을 추가해야 한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. Aspect 클래스 정의
Aspect를 정의하기 위해 @Aspect 애노테이션을 사용한다. 여러 종류의 Advice를 포함하는 Aspect를 만들어 보자.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// Pointcut 표현식: com.example.service 패키지 내의 모든 메서드 실행
private static final String POINTCUT_EXPRESSION = "execution(* com.example.service.*.*(..))";
@Before(POINTCUT_EXPRESSION)
public void logBefore() {
System.out.println("Before method execution");
}
@After(POINTCUT_EXPRESSION)
public void logAfter() {
System.out.println("After method execution");
}
@Around(POINTCUT_EXPRESSION)
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed(); // 메서드 실행
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
@AfterReturning(pointcut = POINTCUT_EXPRESSION, returning = "result")
public void logAfterReturning(Object result) {
System.out.println("After returning: " + result);
}
@AfterThrowing(pointcut = POINTCUT_EXPRESSION, throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("After throwing: " + exception.getMessage());
}
}
3. AOP 설정 활성화
스프링 부트 애플리케이션 클래스에 @EnableAspectJAutoProxy 애노테이션을 추가하여 AOP 설정을 활성화한다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class AopExampleApplication {
public static void main(String[] args) {
SpringApplication.run(AopExampleApplication.class, args);
}
}
Advice 종류와 사용 예시
1. @Before
메서드 실행 전에 로직을 수행한다.
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Before method execution");
}
2. @After
메서드 실행 후에 로직을 수행한다.
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("After method execution");
}
3. @Around
메서드 실행 전후에 로직을 수행한다.
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed(); // 메서드 실행
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
4. @AfterReturning
메서드가 정상적으로 반환된 후에 로직을 수행한다.
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(Object result) {
System.out.println("After returning: " + result);
}
5. @AfterThrowing
메서드가 예외를 던진 후에 로직을 수행한다.
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("After throwing: " + exception.getMessage());
}
결론
AOP는 스프링 프레임워크에서 중요한 프로그래밍 기법으로, 코드의 재사용성과 유지보수성을 향상시킬 수 있다. @AspectJ 애노테이션 스타일을 사용하면 간단하게 AOP를 적용할 수 있으며, 공통 기능을 모듈화하여 코드의 복잡성을 줄일 수 있다.
'Back-End > Spring' 카테고리의 다른 글
Thymeleaf (0) | 2024.05.21 |
---|---|
Spring MVC (0) | 2024.05.18 |
ResultSetExtractor (0) | 2024.05.17 |
@Transactional과 TransactionTemplate (0) | 2024.05.17 |
배치 업데이트(Batch Update) (0) | 2024.05.17 |