반응형
이번 시간에는 횡단 관심사 (AOP)에 대해서 알아보자
횡단 관심사(Aspect-Oriented Programming, AOP)는 프로그래밍에서 자주 등장하는 개념이다. 이 개념의 핵심은 애플리케이션의 여러 부분에서 반복되는 기능들을 하나의 장소에 모아 관리하는 것이다. 예를 들어보면 로깅, 보안, 트랜잭션 처리 같은 기능들이 횡단 관심사에 해당한다.
횡단 관심사(Aspect-Oriented Programming, AOP)란?
1. AOP란 무엇인가?
AOP, 즉 Aspect-Oriented Programming은 프로그래밍에서 매우 중요한 개념 중 하나로, 주로 반복되는 코드와 관심사를 분리하는 데 초점을 맞추고 있다. 이 방식은 소프트웨어 개발에서 흔히 접하는 문제인 코드의 중복을 줄이고, 모듈성을 향상시키는 데 큰 도움을 준다. AOP는 로깅, 트랜잭션 관리, 보안 등과 같이 여러 모듈이나 클래스에 걸쳐 존재하는 기능들을 하나의 장소에서 관리할 수 있게 해 준다. 이를 통해 개발자는 핵심 비즈니스 로직에 더 집중할 수 있고, 코드의 재사용성과 유지보수성도 크게 향상시킬 수 있다.
2. SpringBoot에서 AOP의 중요성
SpringBoot 환경에서 AOP는 특히 중요한 역할을 한다. SpringBoot는 엔터프라이즈 애플리케이션을 빠르고 효율적으로 개발할 수 있는 환경을 제공하지만, 복잡한 애플리케이션에서는 반복되는 코드가 많아질 수 있다. AOP를 사용하면 이러한 반복 코드를 효과적으로 관리할 수 있고, 애플리케이션의 전반적인 구조를 더 깔끔하고 유지보수하기 쉽게 만들 수 있다. 또한, SpringBoot의 AOP 지원은 매우 강력해서 어노테이션 기반의 구성으로 쉽게 AOP를 구현할 수 있다. 이는 개발 과정을 단순화시키고, 개발자가 비즈니스 로직에 더 집중할 수 있게 해 준다.
1. AOP의 기본 개념
1-1. 횡단 관심사(Cross-Cutting Concerns) 이해하기
- 횡단 관심사(Cross-Cutting Concerns)란 어플리케이션의 여러 부분에 공통으로 나타나는 기능들을 말한다. 예를 들면 로깅, 보안 검사, 트랜잭션 관리 등이 있다. AOP를 사용하면 이런 기능들을 한 곳에서 관리하여 코드 중복을 줄이고 유지보수를 용이하게 할 수 있다.
1-2. AOP 주요 용어
- 어드바이스(Advice)
- AOP에서 특정 조인 포인트에서 실행되는 코드 조각을 말한다. 예를 들어, 메서드 호출 전에 실행되는 로직이나, 메소드 실행 후에 처리되는 로직 등이 여기에 해당한다.
@Aspect
@Component
public class LoggingAspect {
// 메소드 실행 전에 로그를 남기는 어드바이스
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before: " + joinPoint.getSignature().getName());
}
// 메소드 실행 후에 로그를 남기는 어드바이스
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After: " + joinPoint.getSignature().getName());
}
}
- 조인 포인트(Join Point)
- Join Point는 AOP의 어드바이스가 적용될 수 있는 애플리케이션의 실행 지점을 말한다. 주로 메소드 호출이 이에 속한다. 위의 예시코드에서 JoinPoint 객체를 사용해 현재 실행 중인 메서드의 정보를 가져오고 있다.
- Join Point는 AOP의 어드바이스가 적용될 수 있는 애플리케이션의 실행 지점을 말한다. 주로 메소드 호출이 이에 속한다. 위의 예시코드에서 JoinPoint 객체를 사용해 현재 실행 중인 메서드의 정보를 가져오고 있다.
- 포인트컷(Pointcut)
- 포인트컷은 어떤 조인 포인트에 어드바이스를 적용할지를 정의하는 표현식을 의미한다. 위 예시의 @Before와 @After 어노테이션에서 사용된 execution(* com.example.service.*.*(..)) 부분이 포인트컷 표현식이다. 이 표현식은 com.example.service 패키지 안의 모든 클래스의 모든 메서드에 어드바이스를 적용하라는 의미이다. (이 부분은 작성방법이 정해져 있어서 조금 더 자세히 알아볼 필요가 있다.)
- 포인트컷은 어떤 조인 포인트에 어드바이스를 적용할지를 정의하는 표현식을 의미한다. 위 예시의 @Before와 @After 어노테이션에서 사용된 execution(* com.example.service.*.*(..)) 부분이 포인트컷 표현식이다. 이 표현식은 com.example.service 패키지 안의 모든 클래스의 모든 메서드에 어드바이스를 적용하라는 의미이다. (이 부분은 작성방법이 정해져 있어서 조금 더 자세히 알아볼 필요가 있다.)
- 어스펙트(Aspect)
- Aspect는 여러 어드바이스와 포인트컷을 하나의 모듈로 모아놓은 것이다. 이를 통해서 횡단 관심사를 구현한다. 위에 있는 LoggingAspect 클래스 자체가 어스펙트의 예시이다. 이 클래스는 로깅을 위한 어드바이스(메서드 실행 전후에 로그를 남기는 로직)와 포인트컷(적용될 메소드를 정의하는 표현식)을 함께 포함하고 있다.
- Aspect는 여러 어드바이스와 포인트컷을 하나의 모듈로 모아놓은 것이다. 이를 통해서 횡단 관심사를 구현한다. 위에 있는 LoggingAspect 클래스 자체가 어스펙트의 예시이다. 이 클래스는 로깅을 위한 어드바이스(메서드 실행 전후에 로그를 남기는 로직)와 포인트컷(적용될 메소드를 정의하는 표현식)을 함께 포함하고 있다.
1-3. AOP의 장점과 사용 시나리오
- AOP는 다음과 같은 장점들을 제공한다.
- 중복 코드 감소: 공통 기능을 한 곳에 정의하여 여러 곳에서 재사용할 수 있어.
- 모듈성 향상: 비즈니스 로직에서 부가적인 관심사를 분리함으로써 코드의 가독성과 유지보수성을 높일 수 있어.
- 유연한 개발: 새로운 어드바이스 추가나 기존 어드바이스 변경이 용이해져, 유연한 개발이 가능해.
- AOP는 특히 다음과 같은 시나리오에서 유용하다.
- 로깅 및 모니터링: 메소드 실행 시 로그를 남기거나 성능을 모니터링하는 경우
- 보안 검사: 메소드 호출 전에 사용자 인증이나 권한 검사를 수행
- 트랜잭션 관리: 비즈니스 로직의 실행을 트랜잭션으로 관리
1-4. 로깅을 위한 AOP 구현 예시
- Spring Boot 어플리케이션에서 모든 컨트롤러 메서드의 실행 시간을 로깅하는 간단한 AOP 어스펙트를 만들었다. @Aspect 어노테이션으로 이 클래스가 어스펙트임을 정의하고, @Pointcut으로 어느 메서드에 적용할지를 정의한다. 이후 @Around 어드바이스를 사용해서 메소드 실행 전후에 로그를 남기는 것을 볼 수 있다.
// 로깅을 위한 어스펙트 정의
@Aspect
@Component
public class LoggingAspect {
// 모든 컨트롤러의 모든 메소드에 적용
@Pointcut("execution(* com.yourapp.controller..*(..))")
public void controllerMethods() {}
// 메소드 실행 전후에 로그 남기기
@Around("controllerMethods()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed(); // 메소드 실행
} finally {
long timeTaken = System.currentTimeMillis() - startTime;
// 메소드 이름과 실행 시간 로깅
System.out.println("Execution of " + joinPoint.getSignature() + " took " + timeTaken + " ms");
}
}
}
2. SpringBoot에서 AOP 구현하기
2-1. 개발 환경 설정
- SpringBoot 프로젝트에서 AOP를 사용하기 위해서는 먼저 개발 환경을 설정해야 해. 이를 위해 Java와 SpringBoot 환경이 설치되어 있어야 하며, IDE로는 IntelliJ IDEA나 Eclipse를 사용할 수 있어.
2-2. AspectJ 의존성 추가
- SpringBoot 프로젝트에서 AOP 기능을 사용하기 위해 AspectJ 관련 의존성을 pom.xml에 추가해야 한다. Gradle 사용 시에는 build.gradle 파일에 다음과 같은 의존성을 추가한다.
dependencies {
// Spring AOP 의존성 작성하기
implementation("org.springframework.boot:spring-boot-starter-aop")
}
2-3. 간단한 AOP 예제
- 트랜잭션 관리 어스펙트
- 트랜잭션 관리를 위한 어스펙트를 추가해 볼 수 있다. 아래 예시에서는 비즈니스 로직의 실행을 트랜잭션으로 관리한다.
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 트랜잭션 시작
Object result = joinPoint.proceed(); // 메소드 실행
// 트랜잭션 커밋
return result;
} catch (Exception e) {
// 트랜잭션 롤백
throw e;
}
}
}
- 로깅 어스펙트 만들기
- 로깅 어스펙트를 만들어 메서드 실행 전후에 로그를 남겨보자. 아래는 간단한 로깅 어스펙트 구현 예시다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.yourapp.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
@After("execution(* com.yourapp.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature());
}
}
- 보안 어스펙트 만들기
- 보안을 검사하는 어스펙트를 만들어보자. 이 예제에서는 메소드 실행 전에 사용자의 권한을 확인하는 로직을 추가해 본다.
@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.yourapp.service.*.*(..))")
public void securityCheck(JoinPoint joinPoint) {
// 보안 검사 로직
System.out.println("Security check for method: " + joinPoint.getSignature());
}
}
2-4. 어드바이스 유형
- Before Advice
- 메서드 실행 전에 특정 작업을 수행한다. 예를 들어, 로깅이나 인증 체크 등이 있다. 아래의 예시코드는 메서드 실행 전에 로깅과 간단한 인증 체크를 수행한다.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
// 로깅
System.out.println("Before method: " + joinPoint.getSignature().getName());
// 간단한 인증 체크
if (!isUserAuthenticated()) {
throw new UnauthorizedAccessException("User not authenticated");
}
}
private boolean isUserAuthenticated() {
// 인증 로직 구현
return true; // 예시를 위해 항상 true를 반환
}
}
- After Returning Advice
- 메서드가 성공적으로 실행된 후 실행되는 로직이다. 주로 결과 데이터의 후처리나 로깅에 사용된다. 예시코드는 메소드가 성공적으로 실행된 후에 결과 데이터를 로깅하고 가공하는 코드이다.
@Aspect
@Component
public class AfterReturningAspect {
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// 결과 데이터 로깅
System.out.println("After returning from method: " + joinPoint.getSignature().getName() + ", Return value: " + result);
// 결과 데이터 가공 (예시)
if (result instanceof List) {
// 데이터 가공 로직
}
}
}
- After Throwing Advice
- 메소드 실행 중 예외가 발생했을 때 실행되는 로직이다. 주로 예외 처리 로직에 활용된다. 아래의 예시코드는 메서드 실행 중 예외가 발생했을 때, 예외를 로깅하고 특정 처리를 하는 코드다.
@Aspect
@Component
public class AfterThrowingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
// 예외 로깅
System.out.println("An exception has been thrown in " + joinPoint.getSignature().getName() + "; Message: " + ex.getMessage());
// 예외에 따른 추가 처리 (예시)
if (ex instanceof SpecificException) {
// 특정 예외 처리 로직
}
}
}
- After Advice
- 메소드 실행 후, 성공/실패 여부에 관계없이 항상 수행되는 작업을 처리한다 (예: 리소스 정리). 아래의 예시코드는 메서드 실행 후 항상 수행되는 클린업 로직이다.
@Aspect
@Component
public class CleanupAspect {
@After("execution(* com.example.service.*.*(..))")
public void doCleanup(JoinPoint joinPoint) {
// 클린업 로직
System.out.println("Performing cleanup after method: " + joinPoint.getSignature().getName());
}
}
- Around Advice
- 메서드 실행 전후 및 예외 발생 시 복잡한 로직을 수행한다 (예: 성능 모니터링). 아래의 예시코드는 메서드 실행 전후를 포함하여 성능 모니터링을 수행하는 코드다.
@Aspect
@Component
public class PerformanceMonitoringAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
// 메소드 실행 전 로직
Object result = joinPoint.proceed(); // 메소드 실행
// 메소드 실행 후 로직
return result;
} finally {
long executionTime = System.currentTimeMillis() - startTime;
// 성능 로깅
System.out.println(joinPoint.getSignature().getName() + " executed in " + executionTime + "ms");
}
}
}
3. 실제 사례 연구
3-1. 트랜잭션 관리
- AOP는 트랜잭션 관리에 자주 사용된다. 예를 들어, 데이터베이스 작업을 하는 메서드에 대해 트랜잭션을 자동으로 시작하고, 성공적으로 완료되면 커밋하거나 실패하면 롤백하는 로직을 AOP로 구현할 수 있다. 이는 코드의 재사용성을 높이고, 트랜잭션 관리 로직을 중앙에서 관리할 수 있게 해 준다.
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 트랜잭션 시작
Object result = joinPoint.proceed(); // 메소드 실행
// 트랜잭션 커밋
return result;
} catch (Exception e) {
// 트랜잭션 롤백
throw e;
}
}
}
- 이 예시에서는 @Transactional 어노테이션이 붙은 메서드에 자동으로 트랜잭션 관리 로직을 적용한다.
3-2. 성능 모니터링
- 성능 모니터링은 AOP의 또 다른 중요한 사용 사례이다. 서비스나 메서드의 실행 시간을 로그로 남겨서 성능을 모니터링하고, 필요한 경우 성능 최적화를 수행할 수 있다. AOP를 사용하면 이러한 로직을 비즈니스 로직과 분리하여 관리할 수 있다.
@Aspect
@Component
public class PerformanceMonitoringAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long endTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " executed in " + (endTime - startTime) + " ms");
}
}
}
- 이 예시에서는 메소드의 실행 시간을 측정하여 로그로 남긴다.
3-3. 예외 로깅
- AOP는 예외 처리와 로깅에도 유용하다. 예외가 발생할 때 자동으로 로그를 남기고, 필요한 경우 추가적인 처리를 할 수 있게 해 준다. 이는 에러 핸들링을 보다 효율적으로 만들어 준다.
@Aspect
@Component
public class ExceptionLoggingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logException(JoinPoint joinPoint, Throwable ex) {
System.err.println("Exception in " + joinPoint.getSignature().getName() + ": " + ex.getMessage());
// 여기에 추가적인 예외 처리 로직을 구현할 수 있음
}
}
- 이 예시에서는 메서드 실행 중 발생한 예외를 로그로 남기고, 필요한 경우 추가적인 처리를 수행한다.
4. 결론
4-1. AOP의 장점
- 코드 중복 감소 : 횡단 관심사를 한 곳에서 관리함으로써, 여러 곳에서 중복되는 코드를 줄일 수 있다.
- 유지보수성 향상 : 비즈니스 로직과 부가적인 관심사를 분리함으로써, 코드의 가독성과 유지보수성이 증가한다.
- 비즈니스 로직과 부가적인 관심사의 분리 : 로깅, 보안, 트랜잭션 관리 등과 같은 부가적인 관심사를 비즈니스 로직에서 분리하여 관리할 수 있다.
4-2. AOP의 단점
- 코드 흐름의 복잡성: AOP의 남용은 코드의 실행 흐름을 이해하기 어렵게 만들 수 있다.
- 성능에 미미한 영향: AOP는 성능에 미미한 영향을 줄 수 있지만, 대부분의 경우 이는 크게 문제 되지 않는다.
4-3. 프로젝트에 AOP를 통합할 때 고려해야 할 사항들
- 남용 방지 : AOP의 과도한 사용은 코드의 실행 흐름을 복잡하게 만들 수 있으므로, 적절한 수준에서 사용하는 것이 중요하다.
- 포인트컷 표현식의 정확성 : AOP를 효과적으로 구현하기 위해선 포인트컷 표현식을 정확하고 명확하게 작성해야 한다.
- 프로젝트의 복잡성과 유지보수성 고려 : AOP를 도입하기 전에 프로젝트의 복잡성과 유지보수성을 고려하여, AOP의 사용이 실제로 이점을 가져다 줄지를 판단해야 한다.
이렇게 횡단 관심사 (AOP)에 대해서 공부를 하면서 정리를 해봤다. 내가 AOP는 사용해 본 것은 @Transactional을 사용할때 많이 써보고 그 이외에는 @ControllerAdvice를 사용할때나 사용해봤지 이것을 @Aspect까지 써보면서 제대로 사용해본 기억은 없다. 이렇게 AOP에 대해서 정리해 보면서 직접 내 코드에도 적용시킬 수 있는 부분들이 있는지 많이 생각해 보게 되었다. 다음에는 한번 적용시킨 후에 글을 작성하도록 하겠다.
반응형
'Spring > Spring 기초 지식' 카테고리의 다른 글
Spring Boot 생성자 주입 알아보기1: @ComponentScan 동작원리 (1) | 2023.12.02 |
---|---|
SpringBoot: 리소스 관리하기 (resource) (1) | 2023.11.25 |
[Spring] @Component와 @Bean의 차이점 (0) | 2023.11.13 |
[Spring] @Component로 스프링 빈 등록하기 (0) | 2023.11.12 |
[Spring] @Bean을 사용한 스프링 빈 등록 (0) | 2023.11.12 |