개발소설

AOP(Aspect Oriented Programming) 본문

Spring Framework

AOP(Aspect Oriented Programming)

ChaeHing 2023. 4. 8. 01:40

AOP (Aspect Oriented Programming)

  • 관점 지향 프로그래밍을 의미
  • Core Concerns (핵심 관심사)에서 횡단 관심사(Cross-Cutting Concern)를 분리함으로써 모듈성을 증가시키는 프로그래밍 기법
  • 여러 객체에 공통으로 적용할 수  있는 기능을 분리하여 반복 작업을 줄이고 핵심 기능 개발에만 집중 할 수 있다.
    • 로깅, 트랜젝션, 보안 등
예를 들자면 어떠한 비즈니스 로직에 실행시간을 측정하는 코드를 만들어서 적용 했는데 이것이 유용해서 
다른 비즈니스로직들에도 이것들을 적용하고 싶을떄 적용해야 하는 비즈니스 로직이 몇천개 혹은 몇만개라면?
그 로직들에 일일히 코드를 추가 하는것은 매우 비효율적 이기 때문에 
횡단(공통)관심사로 만들어 핵심 로직을 수정하지 않고 공통 기능의 구현을 추가하는것이 좋다.

관점 지향을 그림으로 표현 하자면

 

 

AOP를 사용하는 이유

  • OOP(객체 지향 프로그래밍) 방식의 프로그래밍을 했을 때 여러 곳에서 공통적으로 사용되는 부가 기능의 중복 코드가 발생
  • 중복되는 부가 기능에 수정 및 삭제가 필요하게 되면 사용되는 모든 곳에 수정 및 삭제 동작을 해줘야한다.
  • 관심 지향 프로그래밍(AOP)은 OOP 방식의 불필요한 반복을 해결하기 위한 방법
  • AOP는 OOP를 대체하는것이 아니라 보완하는것 이다.

 

AOP 용어

 

애스팩트(Aspect)

  • 여러 객체에 공통으로 적용되는 기능
  • 어드바이스 + 포인트컷을 모듈화하여 애플리케이션에 포함되는 횡단 기능
  • 부가 기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용할지 결정하는 포인트컷(PointCut)을 합친 개념

조인포인트(join point)

  • 조인 포인트는 추상적인 개념이고, AOP를 적용할 수 있는 지점을 의미
  • 클래스 초기화, 객체 인스턴스화, 메소드 호출, 필드 접근, 예외 발생과 같은 애플리케이션 실행 흐름에서의 특정 포인트를 의미
  • 애플리케이션에 새로운 동작을 추가하기 위해 조인포인트에 관심 코드(aspect code)를 추가할 수 있다.
  • 횡단 관심은 조인포인트 전/후에 AOP에 의해 자동으로 추가
  • 스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메소드 실행 지점으로 제한
  • 스프링 AOP는 스프링 컨테이너가 관리할 수 있는 빈에만 aop를 적용

어드바이스(Advice)

  • 조인포인트에서 수행되는 코드, 부가 기능에 해당
  • Aspect를 언제 핵심 코드에 적용할 지를 정의

포인트컷(Pointcut)

  • 조인 포인트 중에서 어드바이스가 적용될 위치를 선별하는 기능
  • 프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 포인트컷으로 선별 가능
  • ** 프록시란? 자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아 주는것(대리인, 대리자)
    • 클라이언트 -> 프록스 -> 타깃

위빙(Weaving)

  • 포인트컷으로 결정한 타겟의 조인 포인트에 어드바이스를 적용
  • Advice를 핵심 코드에 적용 하는것
  • 3가지 적용 방법
    • 컴파일 시점에 코드에 공통 기능 적용
    • 클래스 로딩 시점에 바이트 코드에 공통 기능 적용
    • 런타임 시점에 프록시 객체를 생성하여 공통 기능 적용 - 스프링 aop에서 사용

AOP 프록시(proxy)

  • AOP 기능을 구현하기 위해 만든 프록시 객체
  • 스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시

타겟 (Target)

  • 핵심 기능을 담고 있는 모듈로 부가기능을 부여할 대상
  • Adivce를 받는 객체이고 포인트컷으로 결정

어드바이저(Advisor)

  • 하나의 어드바이스와 하나의 포인트 컷으로 구성
  • 스프링 AOP에서만 사용되는 특별한 용어

 

Advice 종류

  • Before - 조인 포인트 실행 이전에 실행
  • After returning - 조인 포인트가 정상 완료 후 실행
  • After throwing - 메서드가 예외를 던지는 경우에 실행
  • After (finally) - 조인 포인트의 동작(정상 또는 예외)과는 상관없이 실행
  • Around - 메서드 실행 전 & 후, 예외 발생 시점에 공통 기능을 실행, ProceedingJoinPoint를 사용

 

pointcut 표현식

  • AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공
  • 포인트컷에는 여러 지시자가 있지만 execution을 많이 사용하고 나머지는 자주 사용하지 않는다.
@Pointcut("execution(* start.aop.order..*(..))")
private void allOrder(){}
  • excution( 접근제어자[생략가능] 리턴타입 패키지경로.클래스.메서드(파라미터)
    • 리턴타입
      •  * : 모든 리턴 타입 허용 
      • void : 리턴타입이 void인 메서드 선택 
      • !void : 리턴타입이 void가 아닌 메서드 선택
    • 패키지 경로
      • •com.~~~.클래스명 : 정확하게 이 패키지만 선택 
      • com.~~~..클래스명 : com.~~~로 시작하는 모든 패키지 
      • com.~~~..impl.클래스명 : com.~~~.로 시작하고 impl로 끝나는 패키지 선택
    • 클래스
      • 패키지명.클래스명(OrderService) : 해당 클래스만 선택 (OrderService 클래스만 선택)
      • 패키지명.*Service : 클래스이름이 Service로 끝나는 클래스만 선택
      • 패키지명.OrderService+ : 해당클래스(OrderService)로부터 파생된 모든 자식 클래스 선택
        • 인터페이스면 해당 인터페이스를 구현한 모든 클래스
    • 메서드
      • *(..) : 모든 메서드 선택 (기본)
      • get*(..) : 메서드 이름이 get으로 시작하는 모든 메서드 선택
    • 매개변수
      • (..) : 매개변수 개수와 타입의 제약이 없음 (기본)
      • (*) : 반드시 1개의 매개 변수를 가지는 메서드만 선택
    • &&, ||, ! 를 사용하여 결합도 가능
      • start.aop.order 하위 패키지의 메서드(조인포인트)와 클래스명이 Service로 끝나는 클래스의 메서드(조인포인트)
        • 두개의 조건을 모두 만족하는 조인포인트를 설정할때 (&&)로 결합 아래 예제 확인
@Pointcut("execution(* start.aop.order..*(..))")
private void allOrder(){}

@Pointcut("execution(* *..*Service.*(..))")
private void allService(){}

@Pointcut("allOrder() && allService()")
private void orderService() {}

 

AOP 애너테이션 실습

//OrderRepository.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
public class OrderRepository {
    public String save(String itemId) {
        log.info("[orderRepository] 실행");
     
        //저장 로직
        if (itemId.equals("ex")) {
            throw new IllegalStateException("예외 발생");
        } 
        return "ok";
    }
}
// OrderService.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void orderItem(String itemId) {
        log.info("[orderService] 실행");
        orderRepository.save(itemId);
    }
}
// @Aspect로 공통관심사선언
// @Around (포인트컷 표현식)통해 포인트컷 지정
// start.aop.order를 포함한 하위패키지의 모든 메서드(조인 포인트)

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Slf4j
@Aspect
public class Aspect1 {

    @Around("execution(* start.aop.order..*(..))")
    public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("log -> {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}
// @Pointcut으로 포인트컷 따로 분리
// @Around에서 포인트컷 시그니처(서명)로 접근

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Slf4j
@Aspect
public class Aspect2 {

    @Pointcut("execution(* start.aop.order..*(..))")
    private void allOrder(){}

    @Around("allOrder()") 
    public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
				log.info("log -> {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}
// 포인트컷을 공용으로 사용하기위해 외부클래스로 만들기
// 외부에서 호출하기 위해 접근제어자를 public으로 할당
// @Around를 통해 해당클래스경로.포인트컷시그니처로 접근
// @Around("start.aop.order.aop.Pointcuts.allOrder()")

import org.aspectj.lang.annotation.Pointcut;

public class Pointcuts {
    @Pointcut("execution(* start.aop.order..*(..))")
    public void allOrder(){}

    @Pointcut("execution(* *..*Service.*(..))")
    public void allService(){}

    //두가지 조인포인트에 모두 해당하는경우 포인트컷시그니처를 &&로 결합
    @Pointcut("allOrder() && allService()")
    public void orderAndService(){}
}

@Slf4j
@Aspect
public class Aspect4 {

    @Around("start.aop.order.aop.Pointcuts.allOrder()")
    public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("log -> {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }

    @Around("start.aop.order.aop.Pointcuts.orderAndService()") {
    ..
    }

 

간단 정리

  • AOP는 관점지향프로그래밍으로 공통기능과 핵심기능 두가지 관점으로 분리
  • 핵심 기능 로직에 공통 기능을 횡단하도록 설계 
  • 공통기능을 핵심 로직 기능에서 분리함으로써 불필요한 반복작업(공통기능의 내용이 바뀐다거나)을 줄이고 재사용성을 높일수 있다.
  • 조인포인트(Join Point) : AOP를 적용할 수 있는 지점을 의미 - Spring AOP는 메서드 실행 지점만 가능
  • 어드바이스(Advice) : 조인포인트에서 수행되는 코드
  • 포인트컷(Pointcut) : 조인 포인트 중에서 어드바이스가 적용될 위치를 선별하는 기능
  • 애스팩트(Aspect) : 여러 객체에 공통으로 적용되는 기능, 어드바이스 + 포인트컷

 

'Spring Framework' 카테고리의 다른 글

DTO(Data Transfer Object)  (0) 2023.04.13
SpringMVC - Controller  (0) 2023.04.12
Spring MVC  (0) 2023.04.12
DI - 스프링 컨테이너(Spring Container), 빈(bean)  (0) 2023.04.05
Spring Framework 기본 개념  (0) 2023.04.02
Comments