aop

반응형
반응형

AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)의 탄생 배경은 OOP(Object-Oriented Programming)의 한계를 보완하기 위한 필요성에서 출발했습니다.

OOP는 객체 단위로 코드의 모듈화와 재사용성을 높였지만, 프로그램의 다양한 부분에서 공통적으로 필요하지만 특정 객체에 속하지 않는 기능을 처리하는 데 한계를 보였습니다.

이러한 공통적인 기능을 "횡단 관심사(Cross-Cutting Concerns)"라고 합니다. 대표적인 예로는 로그 처리, 트랜잭션 관리, 보안, 예외 처리, 성능 모니터링 같은 것들이 있습니다. 이런 기능들은 여러 클래스와 메서드에서 반복적으로 등장하며, 중복 코드와 유지보수의 어려움을 초래합니다.

AOP는 바로 이 횡단 관심사를 핵심 비즈니스 로직(Primary Concerns)과 명확하게 분리해 코드의 모듈화, 유지보수성, 가독성을 향상시키기 위해 등장했습니다.

AOP의 가장 큰 장점은 비침투적(Non-Invasive) 방식으로 코드를 작성할 수 있다는 것입니다. 즉, 핵심 로직을 건드리지 않고도 부가적인 기능을 유연하게 적용할 수 있습니다.

→ 횡단 관심사는 무엇인가? 하나의 어플리케이션의 여러 부분에 걸쳐 있는 기능

예를 들어.. 은행 시스템이라고 하면

핵심 관심사 (Core Concerns) : 입금, 출금, 이체

횡단 관심사 (Cross-Cutting Concerns) : 입금, 출금, 이체 시 동작되는 보안 처리 / 예외 처리

  • 핵심 관심사 (Core Concerns) : 입금, 출금, 이체
  • 횡단 관심사 (Cross-Cutting Concerns) : 입금, 출금, 이체 시 동작되는 보안 처리 / 예외 처리

뭔가 횡단 관심사는 함께 서로 고민거리를 고민거리를 공유하는 관심사라고 생각이 든다. 예를 들어 트랜잭션 같은 경우도 db를 연동하는 경우라면 전역적으로 트랜잭션이 반드시 필요하다고 여겨진다. 왜냐하면 트랜잭션에 데이터를 원복을 시키기는 기능이 들어가있는데 만약에 트랜잭션을 개발자가 일일이 손수 넣어야 한다면 이것또한 큰 고민거리라 생각이 든다. 트랜잭션이 발생하는 지점을 한번에 처리할 수 있는 방법이 횡단 관심사가 아닐까 생각이 든다.

AOP의 주요 목적:

  1. 중복 코드 제거: 여러 클래스에 중복되는 로직을 하나의 Aspect로 모아 관리.
  2. 관심사 분리: 비즈니스 로직과 부가적인 기능을 분리해 코드의 가독성과 유지보수성 향상.
  3. 유지보수성 향상: 하나의 장소에서 공통 기능을 변경하면 전반적으로 적용됨.
  4. 결합도 감소: 핵심 로직과 공통 기능이 독립적으로 존재하므로 코드 간의 의존성이 줄어듦.

핵심 개념

  • Aspect: 횡단 관심사를 모듈화한 것(예: 로깅, 트랜잭션 관리).
    • 여기서 모듈은 특정한 역할을 하는 보조 구성 요소를 뜻한다.
  • Advice: 실제로 수행될 부가 기능(메서드 실행 전, 후, 예외 발생 시 등).
    • @Before: 메서드 실행 전에 동작.
    • @After: 메서드 실행 후에 항상 동작.
    • @AfterReturning: 메서드가 정상적으로 실행된 후 동작.
    • @AfterThrowing: 메서드에서 예외가 발생했을 때 동작.
    • @Around: 메서드 실행 전후 모두 동작하며, 실행을 직접 제어 가능.
  • Pointcut: Advice가 실행될 위치(메서드 실행 시점, 특정 어노테이션 등).
    • execution(* 패키지명.클래스명.메서드명(..)) : 특정 메서드 실행 시점.
    • within(패키지명..*) : 특정 패키지 내 모든 클래스에 적용.
    • @annotation(어노테이션명) : 특정 어노테이션이 붙은 메서드에 적용.
  • JoinPoint: Advice가 적용될 실제 실행 지점.
    • getSignature() : 메서드 이름, 리턴 타입 등의 정보.
    • getArgs() : 메서드의 인자 목록.
    • getTarget() : 실제 실행되는 객체(프록시가 아닌 원본 객체).
  • Weaving: Advice와 비즈니스 로직을 결합하는 과정(컴파일, 로드 타임, 런타임 중 이루어짐).

장점

  • 코드의 중복 제거예를 들어, 여러 메서드에서 동일한 트랜잭션 시작/커밋을 코드에 중복해서 작성하는 대신, AOP로 한 곳에서 정의하면 됩니다.
  • AOP는 횡단 관심사(예: 로깅, 트랜잭션, 보안 등)를 비즈니스 로직과 분리할 수 있게 해주기 때문에, 동일한 기능을 여러 곳에 반복적으로 작성할 필요가 없어요.
  • 유지보수성 향상
  • 횡단 관심사 코드를 한 곳에 모아두면, 유지보수가 더 쉬워져요. 예를 들어, 로깅 방식을 변경하고 싶을 때, 코드 곳곳을 수정할 필요 없이 하나의 Aspect만 수정하면 됩니다.
  • 비즈니스 로직의 깔끔한 분리
  • AOP를 사용하면 비즈니스 로직과 공통 기능을 명확하게 분리할 수 있어 코드가 더 깔끔하고 이해하기 쉬워집니다. 비즈니스 로직에만 집중할 수 있고, 공통 기능은 별도로 관리되므로 가독성도 좋아지죠.
  • 선언적 프로그래밍
  • AOP는 명령형이 아닌 선언적으로 기능을 추가할 수 있기 때문에, 코드 흐름을 더 명확하게 이해할 수 있어요. "이 메서드가 호출될 때마다 로깅을 하겠다"는 선언만 하면 스프링이 알아서 해줍니다.
  • 확장성
  • 새로운 공통 기능을 추가할 때 비즈니스 로직을 수정하지 않고, 새로운 Aspect만 추가하면 되기 때문에 확장성이 뛰어납니다. 예를 들어, 보안 기능이나 로깅 기능을 추가할 때, 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.

단점

  1. 디버깅 어려움
  2. AOP의 동작 방식이 보이지 않아서(특히 위빙 과정), 디버깅할 때 어디서 문제가 발생했는지 찾기 어렵습니다. 특히 런타임에 프록시 객체가 생성되기 때문에, 문제가 발생하면 원인 파악이 까다로울 수 있어요.
  3. 학습 곡선
  4. AOP는 추상화가 높은 기술이라, 처음 접하는 사람에게는 개념적으로 이해하기 어려운 부분이 있을 수 있어요. Aspect, Join Point, Pointcut 같은 개념을 이해하고, 어떻게 적용되는지 익히는 데 시간이 걸릴 수 있습니다.
  5. 성능 문제
  6. AOP는 런타임에 프록시 객체를 생성하고, 이 객체를 통해 비즈니스 로직을 호출하게 되므로 성능 오버헤드가 발생할 수 있어요. 특히 고빈도 호출이 있는 메서드에 AOP를 적용하면 성능에 영향을 줄 수 있습니다.
  7. 복잡성 증가
  8. AOP를 과도하게 사용하면 코드 흐름을 추적하기 어려운 상황이 발생할 수 있어요. 많은 Aspect가 적용되면, 시스템의 흐름을 이해하기 어렵고, 코드가 복잡해질 수 있습니다.
  9. 스프링 의존성
  10. AOP는 스프링과 같은 프레임워크에 의존하는 기술이기 때문에, 스프링을 사용하지 않는 환경에서는 사용할 수 없다는 단점이 있습니다. 즉, AOP를 잘 사용하려면 스프링의 AOP 시스템에 대한 이해가 필요해요.

프록시 객체 생성 방법

  1. jdk 동적 프록시
  • 자바에서 제공하는 기능으로 인터페이스를 기반으로 객체를 동적으로 생성되어진다. 그 객체에 특정 부가기능(로깅, 트랜잭션 관리 등)을 추가할 수 있게 해줍니다. 이 방식은 주로 인터페이스가 존재하는 경우에 사용됩니다. JDK 동적 프록시의 핵심은 자바의 java.lang.reflect.Proxy 클래스와 InvocationHandler 인터페이스를 활용하여, 메서드 호출 시점에 부가기능을 적용할 수 있다는 점입니다.

인터페이스가 존재하지 않는 경우

Exception in thread "main" java.lang.IllegalArgumentException: PaymentServiceImpl is not an interface
  1. cglib

CGLIB의 특징

  • 클래스를 상속하여 프록시 생성: CGLIB는 인터페이스 기반의 프록시가 아니라 구체적인 클래스를 상속하여 동적 프록시를 만듭니다. 그래서 인터페이스가 없더라도 프록시를 생성할 수 있습니다.
  • 메서드 오버라이딩: CGLIB는 실제 객체의 메서드를 오버라이드하여 부가기능을 넣습니다.
  • 성능: CGLIB는 바이트코드를 조작하여 동적으로 클래스를 생성하므로 JDK 동적 프록시보다 성능이 약간 떨어질 수 있습니다. 그러나 인터페이스가 없는 클래스에 대해서는 유용하게 사용될 수 있습니다.

CGLIB 프록시 vs JDK 동적 프록시

1. 인터페이스 유무

  • JDK 동적 프록시: 인터페이스 기반으로 프록시 객체를 생성합니다. 인터페이스가 있어야만 사용할 수 있습니다.
  • CGLIB: 클래스 기반으로 프록시 객체를 생성합니다. 인터페이스가 없고 구체적인 클래스를 상속해서 프록시를 만들 수 있습니다.

2. 성능

  • JDK 동적 프록시: 성능이 상대적으로 빠르지만, 인터페이스가 필요합니다.
  • CGLIB: 성능이 상대적으로 느릴 수 있습니다. 바이트코드를 조작하여 동적으로 클래스를 생성하기 때문입니다.

3. 메서드 호출

  • JDK 동적 프록시: InvocationHandler에서 method.invoke(target, args)로 실제 메서드를 호출합니다.
  • CGLIB: MethodInterceptor의 invokeSuper()로 실제 메서드를 호출합니다.

CGLIB가 사용되는 경우

  • 인터페이스가 없는 클래스에 AOP 적용: JDK 동적 프록시를 사용할 수 없는 경우 CGLIB가 사용됩니다. 예를 들어, @Service와 같이 인터페이스 없이 직접 구현된 클래스에 AOP를 적용하려면 CGLIB가 사용됩니다.
  • 상속을 활용한 AOP: CGLIB는 클래스 상속을 기반으로 하기 때문에, 클래스 레벨에서 AOP를 적용하고자 할 때 유용합니다.

설정: @EnableAspectJAutoProxy(proxyTargetClass = false)을 하게 되면 강제로 jdk 동적 프록시와 cglib을 강제적으로 결정할 수 있다.

포인트컷 문법

  1. execution():
    • 가장 많이 사용되는 포인트컷 표현식입니다. 메서드 실행 지점을 포인트컷으로 지정합니다.
    • execution은 특정 메서드가 실행될 때 AOP가 적용되는 지점을 지정합니다.
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    • *: 모든 반환 타입
    • com.example.service.*.*(..): com.example.service 패키지의 모든 클래스의 모든 메서드에 대해 AOP를 적용하라는 의미입니다.
    • (..): 메서드의 매개변수가 아무리 많든 적든 상관없이 모든 매개변수에 대해 적용됩니다.
  2. within():
    • 포인트컷을 특정 클래스나 패키지에 제한할 수 있습니다. within을 사용하면 특정 클래스, 인터페이스, 또는 패키지 내에서만 포인트컷이 적용됩니다.
    @Pointcut("within(com.example.service.*)")
    public void serviceLayer() {}
    
    • 이 예시는 com.example.service 패키지 내의 모든 클래스에 적용됩니다.
  3. @annotation():
    • 메서드에 특정 어노테이션이 있는 경우에만 적용됩니다.
    @Pointcut("@annotation(com.example.annotation.Loggable)")
    public void loggableMethod() {}
    
    • 이 예시는 @Loggable 어노테이션이 있는 메서드에만 AOP가 적용됩니다.
  4. args():
    • 메서드의 매개변수 타입을 기준으로 포인트컷을 지정할 수 있습니다.
    @Pointcut("args(java.lang.String, ..)")
    public void stringArgumentMethod() {}
    
    • 이 예시는 첫 번째 매개변수가 String 타입인 메서드에만 적용됩니다.
  5. this():
    • 포인트컷이 적용될 객체의 타입을 기준으로 지정할 수 있습니다. this는 현재 객체의 타입을 검사합니다.
    @Pointcut("this(com.example.service.MyService)")
    public void myServiceMethods() {}
    
    • 이 예시는 com.example.service.MyService 타입의 객체에 대해 AOP가 적용됩니다.
  6. target():
    • 포인트컷이 적용될 대상 객체를 기준으로 설정할 수 있습니다. 주로 인터페이스 기반의 AOP에서 사용됩니다.
    @Pointcut("target(com.example.service.MyService)")
    public void myServiceTarget() {}
    
    • 이 예시는 MyService 인터페이스를 구현하는 객체에 대해 AOP가 적용됩니다.
  7. @within():
    • 메서드가 특정 어노테이션을 포함하는 클래스에 있을 때 AOP를 적용합니다.
    @Pointcut("@within(com.example.annotation.Transactional)")
    public void withinTransactionalClass() {}
    
    • 이 예시는 @Transactional 어노테이션이 클래스에 포함된 메서드들에 대해 AOP가 적용됩니다.
  8. @target():
    • 특정 어노테이션이 포함된 객체에만 적용됩니다.
    @Pointcut("@target(com.example.annotation.Transactional)")
    public void targetTransactional() {}
    
    • 이 예시는 @Transactional 어노테이션이 붙은 객체의 메서드들에 AOP가 적용됩니다.


출처:
https://hellomooneekim.netlify.app/%ED%9A%A1%EB%8B%A8%EA%B4%80%EC%8B%AC%EC%82%AC/

 

횡단관심사, 그게 뭔데요

학습 배경 먼저 ‘횡단관심사’라는 키워드에 관심을 가지게 된 계기는, 여러개의 컴포넌트 혹은 페이지가 중복된 일을 가지고 있는 코드에서 비롯되었다. 지금 레벨4인데..레벨2에 기록해놨던

hellomooneekim.netlify.app

chat gpt

반응형

'SPRING START!' 카테고리의 다른 글

API GATE WAY (1)  (0) 2021.10.04
생성자 주입을 사용해야 되는 이유  (0) 2021.09.22
AOP  (0) 2021.08.25
DI 와 IOC  (0) 2021.08.14
이벤트 발생시키기!!  (0) 2021.01.12

댓글

Designed by JB FACTORY