스프링

스프링 핵심 AOP!

그zi운아이 2024. 1. 3. 18:04

AOP의 목적과 중요성

AOP: 프로그래밍에서 공통적인 관심사를 핵심 비즈니스 로직으로부터 분리하여 모듈화하는 프로그래밍 패러다임입니다. 이는 코드 중복을 줄이고 유지보수를 용이하게 합니다.

  1. 코드 중복 감소: 로깅, 보안, 트랜잭션 관리 등과 같은 반복적인 코드를 중앙화함으로써, 코드 중복을 크게 줄일 수 있습니다.
  2. 유지보수의 용이성: 관심사가 분리되어 있기 때문에, 수정이 필요한 경우 한 곳에서만 변경하면 전체 시스템에 적용됩니다.
  3. 확장성 향상: 새로운 기능이나 로직을 추가할 때 기존 코드를 변경하지 않고도 확장할 수 있습니다.
  4. 가독성 및 유지보수 향상: 핵심 로직과 부가 기능이 분리되어 있어 코드의 가독성이 향상되고, 유지보수가 용이해집니다.

핵심 개념:

  1. Aspect
    • 정의: 여러 객체에 걸쳐있는 횡단 관심사(Cross-cutting Concern)를 모듈화한 클래스입니다.
    • 예시: 로깅, 보안, 트랜잭션 관리 등이 Aspect로 모듈화될 수 있습니다.
      • 로깅 Aspect 예시: 모든 서비스 메소드 호출 전후에 로그를 기록하는 기능을 Aspect로 정의할 수 있습니다.
  2. Join Point
    • 정의: Aspect가 적용될 수 있는 프로그램의 지점입니다.
    • 예시: 메소드 호출, 객체 생성, 필드 접근 등이 Join Point가 될 수 있습니다.
      • 메소드 호출 Join Point 예시: 서비스 클래스의 saveData() 메소드 시작 시점.
  3. Advice
    • 정의: 특정 Join Point에서 실행되는 코드입니다.
    • 종류: Before, After, After Returning, After Throwing, Around.
      • Around Advice 예시: 메소드 실행 전후에 트랜잭션을 시작하고 커밋하는 로직.
  4. Pointcut
    • 정의: 여러 Join Point 중에서 Advice가 적용될 위치를 정의합니다.
    • 표현식 사용: 표현식을 통해 특정 메소드나 클래스 패턴에 맞는 Join Point를 선택합니다.
      • Pointcut 예시: execution(* com.mycompany.service.*.*(..))는 com.mycompany.service 패키지 내 모든 메소드에 적용됩니다.
  5. Target Object
    • 정의: Advice가 적용되는 객체입니다.
    • 프록시 사용: 보통 프록시 객체를 통해 원본 객체에 대한 접근을 제어합니다.
      • Target Object 예시: 트랜잭션 관리가 필요한 OrderService 클래스의 인스턴스.
  6. AOP Proxy
    • 정의: AOP 프레임워크에 의해 생성된 객체로, 원본 객체를 대신하여 Advice를 적용합니다.
    • 프록시 종류: JDK 동적 프록시와 CGLIB 프록시가 주로 사용됩니다.
      • AOP Proxy 예시: OrderService에 트랜잭션 관리 Advice를 적용한 JDK 동적 프록시 객체.

프록시(Proxy) in AOP

  • 프록시란?: 다른 객체에 대한 접근을 제어하는 래퍼(wrapper) 또는 대리 객체입니다.
  • 사용 이유: 보안, 로깅, 트랜잭션 등의 부가 기능을 객체에 추가하기 위해 사용됩니다.

정적 프록시(Static Proxy)

  • 특징: 미리 정의된 코드를 사용해 프록시 객체를 생성합니다.
  • 단점: 유연성이 떨어지고, 중복 코드가 발생합니다.
  •  
public class MyServiceStaticProxy implements MyService {
    private MyService target;

    public MyServiceStaticProxy(MyService target) {
        this.target = target;
    }

    @Override
    public void performAction() {
        // 부가 기능 수행 
        target.performAction();
       // 부가 기능 수행
    }
}

MyService service = new MyServiceStaticProxy(new MyServiceImpl());
service.performAction();

다이내믹 프록시(Dynamic Proxy)

  • 개선: 정적 프록시의 단점을 보완하기 위해 등장했습니다.
  • 특징: 런타임에 프록시 객체를 생성합니다.
  • 장점: 유연하며, 여러 클래스와 인터페이스에 걸쳐 재사용 가능합니다.
  • 종류: JDK 동적 프록시와 CGLIB 방식이 있습니다.
  •  
public interface MyService {
    void performAction();
}

public class MyServiceImpl implements MyService {
    public void performAction() {
        // 실제 작업 수행
    }
}

MyService service = (MyService) Proxy.newProxyInstance(
    MyServiceImpl.class.getClassLoader(),
    new Class[]{MyService.class},
    new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 부가 기능 수행
            return method.invoke(new MyServiceImpl(), args);
        }
    }
);

프록시 팩토리 빈(Proxy Factory Bean)

  • 정의: 스프링에서 제공하는 팩토리 빈으로, 다이내믹 프록시를 생성하고 관리합니다.
  • 사용 이유: 다양한 Advice와 Pointcut을 적용하여 AOP 구성을 유연하게 합니다.
  • 장단점: 유연한 AOP 구성이 가능하지만, 프록시 생성 과정이 복잡할 수 있습니다.
  • 동작 원리:
    • ProxyFactoryBean은 명시적으로 프록시를 생성하는 데 사용됩니다.
    • 빈에 대상 객체(target)와 적용할 Advisor 또는 Advice를 설정합니다.
    • ProxyFactoryBean은 이 정보를 사용하여 프록시 객체를 생성하고, 프록시는 원본 객체의 메소드 호출 시 Advice를 실행합니다.
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(new MyServiceImpl());
proxyFactoryBean.addAdvice(new MyAdvice());
MyService myServiceProxy = (MyService) proxyFactoryBean.getObject();

자동 프록시 생성기(DefaultAdvisorAutoProxyCreator)

  • 정의: 스프링 컨테이너에서 자동으로 프록시 객체를 생성하는 빈입니다.
  • 역할: 어드바이저에 따라 Advice와 Pointcut이 적용된 프록시를 자동으로 생성합니다.
  • 동작 원리 :
    • 스프링 빈 생명주기의 일부로 통합되어 있으며, 스프링 컨테이너가 빈을 생성할 때마다 작동합니다.
    • 빈을 생성할 때, DefaultAdvisorAutoProxyCreator는 해당 빈이 적용 가능한 어떤 Advisor(Advice와 Pointcut의 조합)가 있는지 검사합니다.
    • 적절한 Advisor가 있으면, 이를 기반으로 프록시를 자동으로 생성합니다. 이 프록시는 원본 빈의 메소드 호출을 가로채어 Advisor에 정의된 Advice 로직을 실행합니다.

AOP의 발전 과정 및 이유

1. 정적 프록시(Static Proxy) 사용

초기 AOP 구현

  • 사용 방법: 명시적으로 프록시 클래스를 작성하고, 대상 객체를 감싸는 방식으로 구현.
  • 장점: 단순하고 명확한 구현 방법.
  • 단점 및 한계:
    • 중복 코드 문제: 각 객체마다 별도의 프록시 클래스를 작성해야 하므로 중복 코드가 많아집니다.
    • 유연성 부족: 새로운 메소드나 클래스를 추가할 때마다 새로운 프록시를 만들어야 합니다.
    • 유지보수 어려움: 대규모 시스템에서 프록시 관리가 복잡해집니다.

2. 다이내믹 프록시(Dynamic Proxy) 도입

런타임 프록시 생성

  • 방법: 런타임에 프록시 객체를 동적으로 생성하는 방식.
  • 장점:
    • 유연성 향상: 런타임에 다양한 객체에 대해 프록시를 생성할 수 있어, 유연성이 크게 증가합니다.
    • 코드 중복 감소: 하나의 프록시 생성 메커니즘을 통해 다양한 객체를 처리할 수 있습니다.
  • 단점 및 한계:
    • 구성의 복잡성: 프록시 객체를 생성하고 관리하는 코드가 복잡해질 수 있습니다.
    • 학습 곡선: 개발자가 이해하고 사용하기 위한 학습 곡선이 증가합니다.

3. 프록시 팩토리 빈(Proxy Factory Bean) 사용

간편한 프록시 관리

  • 목적: 다이내믹 프록시의 복잡성을 줄이고, 관리를 용이하게 하는 것.
  • 작동 방식: 스프링이 제공하는 ProxyFactoryBean을 사용하여 프록시를 생성하고 관리.
  • 장점:
    • 간소화된 프록시 생성: 프록시 생성 로직을 스프링이 처리하므로 개발자는 비즈니스 로직에 집중할 수 있습니다.
    • 다양한 AOP 기능 지원: Advice, Pointcut 등을 쉽게 적용하여 강력한 AOP 구성이 가능합니다.
  • 단점 및 한계:
    • 추가적인 구성 필요: ProxyFactoryBean을 사용하기 위한 스프링 구성이 필요합니다.

4. 자동 프록시 생성기(DefaultAdvisorAutoProxyCreator) 도입

AOP 구현의 극대화

  • 목적: AOP 구현의 자동화와 간소화.
  • 작동 방식: 스프링의 DefaultAdvisorAutoProxyCreator가 빈의 생성 과정에서 자동으로 프록시를 생성합니다.
  • 장점:
    • 전체 자동화: 프록시 생성과 관련된 모든 작업이 자동화되어, 개발자는 AOP 관련 설정만 집중하면 됩니다.
    • 편리한 AOP 적용: @Aspect 어노테이션을 사용한 AspectJ 스타일의 AOP 구현이 가능해집니다.
  • 단점 및 한계:
    • 이해와 제어의 복잡성: 내부적인 자동화 과정으로 인해 AOP의 작동 방식을 이해하고 제어하는 것이 복잡해질 수 있습니다.

간단한 AOP 예시

AOP 관련 Annotation

1. @Aspect

  • 용도: 클래스가 AOP의 Aspect임을 나타내는 어노테이션. Aspect는 공통 기능(로깅, 보안 검사 등)을 정의하는 모듈

2. @Before

  • 용도: 조인 포인트(메소드 실행 등) 실행 전에 실행될 어드바이스를 정의

3. @After

  • 용도: 조인 포인트 실행 후에 실행될 어드바이스를 정의.  메소드가 성공적으로 완료되었거나 예외가 발생했을 때 실행

4. @AfterReturning

  • 용도: 조인 포인트가 성공적으로 결과값을 반환한 후에 실행될 어드바이스를 정의.

5. @AfterThrowing

  • 용도: 조인 포인트 실행 중 예외가 발생했을 때 실행될 어드바이스를 정의

6. @Around

  • 용도: 조인 포인트의 실행 전후에 실행될 어드바이스를 정의 가장 유연한 어드바이스로, 메소드 실행 전과 후의 로직을 모두 제어 가능

7. @Pointcut

  • 용도: 재사용 가능한 포인트컷을 정의. 포인트컷은 어드바이스가 적용될 위치(메소드, 클래스 등)를 AspectJ 표현식으로 지정.

마무리

스프링 AOP의 기본 개념과 간단한 예시를 통해  AOP가  프로그래밍에 어떻게 적용될 수 있는지를 살펴보았습니다. AOP는 코드 중복을 줄이고, 유지보수를 용이하게 하며, 객체지향적인 설계를 지원하는 강력한 도구입니다.

스프링을 공부하면서 AOP는 제게 가장 어려운 부분 중 하나였습니다. 처음에는 단순히 '어떻게 사용하는가'에 집중했었는데, 왜 AOP가 필요하고, 어떤 문제를 해결하기 위해 등장했는지 이해하는 것이 중요하고 기술 자체가 목적이 되어서는 안 되며, 항상 '왜'라는 질문을 던지며 코드를 작성해야 한다는 것을 깨달았습니다. 

'스프링' 카테고리의 다른 글

토비의 스프링 | 8장 스프링이란 무엇인가  (0) 2024.01.10
DataAccessException이란?  (0) 2023.12.29
빈(Bean)을 등록하는 여러가지 방법!  (0) 2023.12.20
스프링 빈이란?  (0) 2023.12.20