[Spring] AOP: 횡단 관심사 쉽게 이해하기

2023. 11. 13. 11:28·Spring/Spring 기초 지식
반응형

이번 시간에는 횡단 관심사 (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 객체를 사용해 현재 실행 중인 메서드의 정보를 가져오고 있다.

  • 포인트컷(Pointcut)
    • 포인트컷은 어떤 조인 포인트에 어드바이스를 적용할지를 정의하는 표현식을 의미한다. 위 예시의 @Before와 @After 어노테이션에서 사용된 execution(* com.example.service.*.*(..)) 부분이 포인트컷 표현식이다. 이 표현식은 com.example.service 패키지 안의 모든 클래스의 모든 메서드에 어드바이스를 적용하라는 의미이다. (이 부분은 작성방법이 정해져 있어서 조금 더 자세히 알아볼 필요가 있다.)

  • 어스펙트(Aspect)
    • 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의 장점

  1. 코드 중복 감소 :  횡단 관심사를 한 곳에서 관리함으로써, 여러 곳에서 중복되는 코드를 줄일 수 있다.
  2. 유지보수성 향상 :  비즈니스 로직과 부가적인 관심사를 분리함으로써, 코드의 가독성과 유지보수성이 증가한다.
  3. 비즈니스 로직과 부가적인 관심사의 분리 :  로깅, 보안, 트랜잭션 관리 등과 같은 부가적인 관심사를 비즈니스 로직에서 분리하여 관리할 수 있다.

4-2. AOP의 단점

  1. 코드 흐름의 복잡성: AOP의 남용은 코드의 실행 흐름을 이해하기 어렵게 만들 수 있다.
  2. 성능에 미미한 영향: AOP는 성능에 미미한 영향을 줄 수 있지만, 대부분의 경우 이는 크게 문제 되지 않는다.

4-3. 프로젝트에 AOP를 통합할 때 고려해야 할 사항들

  1. 남용 방지 :  AOP의 과도한 사용은 코드의 실행 흐름을 복잡하게 만들 수 있으므로, 적절한 수준에서 사용하는 것이 중요하다.
  2. 포인트컷 표현식의 정확성 :  AOP를 효과적으로 구현하기 위해선 포인트컷 표현식을 정확하고 명확하게 작성해야 한다.
  3. 프로젝트의 복잡성과 유지보수성 고려 :  AOP를 도입하기 전에 프로젝트의 복잡성과 유지보수성을 고려하여, AOP의 사용이 실제로 이점을 가져다 줄지를 판단해야 한다.

 

 

이렇게 횡단 관심사 (AOP)에 대해서 공부를 하면서 정리를 해봤다. 내가 AOP는 사용해 본 것은 @Transactional을 사용할때 많이 써보고 그 이외에는 @ControllerAdvice를 사용할때나 사용해봤지 이것을 @Aspect까지 써보면서 제대로 사용해본 기억은 없다. 이렇게 AOP에 대해서 정리해 보면서 직접 내 코드에도 적용시킬 수 있는 부분들이 있는지 많이 생각해 보게 되었다. 다음에는 한번 적용시킨 후에 글을 작성하도록 하겠다.

 

 

 

 

 

 

Spring의 핵심 이해: @Bean의 기본과 활용

이번 포스트에는 @Bean에 대해 소개한다. Spring Framework에서 @Bean은 매우 중요한 개념이다. 이는 개발자가 직접 제어할 수 없는 외부 라이브러리나 복잡한 구성이 필요한 객체를 스프링의 관리하에

curiousjinan.tistory.com

 

 

Maven 이해하기: Java프로젝트의 빌드와 관리의 기본

이번 포스트에서는 Maven에 대해서 알아보자 이전 회사에서 작업을 할 때 SpringFramework3.x.x 버전을 사용하고 Maven을 사용했던 기억이 있다. 이때 첫 프로젝트를 하면서 요즘에는 다 Gradle을 사용하는

curiousjinan.tistory.com

 

반응형

'Spring > Spring 기초 지식' 카테고리의 다른 글

SpringBoot: 리소스 관리하기 (resource)  (1) 2023.11.25
Spring Boot 3 및 Spring Batch 5에서 배치 테이블 자동 생성 문제 해결하기  (0) 2023.11.23
[Spring] @Component와 @Bean의 차이점  (0) 2023.11.13
[Spring] @Component로 스프링 빈 등록하기  (0) 2023.11.12
[Spring] JAR와 WAR 이해하기  (0) 2023.11.10
'Spring/Spring 기초 지식' 카테고리의 다른 글
  • SpringBoot: 리소스 관리하기 (resource)
  • Spring Boot 3 및 Spring Batch 5에서 배치 테이블 자동 생성 문제 해결하기
  • [Spring] @Component와 @Bean의 차이점
  • [Spring] @Component로 스프링 빈 등록하기
Stark97
Stark97
문의사항 또는 커피챗 요청은 링크드인 메신저를 보내주세요! : https://www.linkedin.com/in/writedev/
  • Stark97
    오늘도 개발중입니다
    Stark97
  • 전체
    오늘
    어제
    • 분류 전체보기 (240)
      • 개발지식 (20)
        • 스레드(Thread) (8)
        • WEB, DB, GIT (3)
        • 디자인패턴 (8)
      • JAVA (21)
      • Spring (88)
        • Spring 기초 지식 (35)
        • Spring 설정 (6)
        • JPA (7)
        • Spring Security (17)
        • Spring에서 Java 활용하기 (8)
        • 테스트 코드 (15)
      • 아키텍처 (5)
      • MSA (14)
      • DDD (7)
      • gRPC (9)
      • Apache Kafka (18)
      • DevOps (23)
        • nGrinder (4)
        • Docker (1)
        • k8s (1)
        • 테라폼(Terraform) (12)
      • AWS (32)
        • ECS, ECR (14)
        • EC2 (2)
        • CodePipeline, CICD (8)
        • SNS, SQS (5)
        • RDS (2)
      • notion&obsidian (3)
  • 링크

    • notion기록
    • 깃허브
    • 링크드인
  • hELLO· Designed By정상우.v4.10.0
Stark97
[Spring] AOP: 횡단 관심사 쉽게 이해하기
상단으로

티스토리툴바