본문 바로가기

Web/Spring boot

[Spring] AOP와 @Transactional의 동작원리

출처 : https://velog.io/@ann0905/AOP%EC%99%80-Transactional%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

 

[Spring] AOP와 @Transactional의 동작 원리

오늘은 @Transactional의 동작 원리를 AOP와 함께 좀 더 자세하게 조사해보려고 한다.여기서 다루는 내용은 다음과 같다.AOP란 무엇이며 왜 사용하는가Spring AOP는 왜 프록시를 사용하는가@Transactional은

velog.io

위 블로그 및 공식문서에 작성된 내용을 보고 정리한 내용입니다.

 


AOP(Aspect Oriented Programming)

흩어진 관심사를 별도의 클래스로 모듈화하여 아래의 문제들을 해결하고, 결과적으로 OOP를 더욱 잘 지킬 수 있도록 도움을 주는 것이 AOP이다.

 

AOP 없이 흩어진 관심사를 처리할 경우 생기는 문제점들

  • 여러 곳에서 반복적인 코드를 작성해야 한다.
  • 코드가 변경될 경우 여러 곳에 가서 수정이 필요하다.
  • 주요 비즈니스 로직과 부가 기능이 곳에 섞여 가독성이 떨어진다.

AOP의 주요개념

  • Aspect: Advice + PointCut로 AOP의 기본 모듈
  • Advice: Target에 제공할 부가 기능을 담고 있는 모듈
  • Target: Advice이 부가 기능을 제공할 대상 (Advice가 적용될 비즈니스 로직)
  • JointPoint: Advice가 적용될 위치
  • PointCut: Target 지정하는 정규 표현식

Spring AOP

Spring AOP는 기본적으로 프록시 방식(객테를 대행하는 객체를 통해서 대상객체에 접근)으로 동작한다.

 

Spring AOP는 왜 프록시 방식을 사용하는가?

  • 프록시 객체없이 Target객체를 사용할 경우 Target 클래스 안에 부가기능을 호출하는 로직이 포함되기 때문에, 여러 곳에서 반복적으로 Aspect를 호출해야하고, 그로 인해 유지보수성이 크게 떨어진다.(AOP를 적용하지 않았을 때와 동일한 문제 발생)

JDK Proxt와 CGLib Proxy

두 방식의 가장 큰 차이점은 Target의 어떤 부분을 상속 받아서 프록시를 구현하느냐에 있다.

JDK Proxy

  • Target의 상위 인터페이스를 상속 받아 프록시를 만든다.
  • 인터페이스를 구현한 클래스가 아니면 의존할 수 없다.
    • Target에서 다른 구체 클래스에 의존하고 있다면, JDK 방식에서는 그 클래스(빈)을 찾을 수 없어 런타임 에러가 발생한다.
    • 우리가 서비스 계층에서 인터페이스 -> XXXImpl 클래스를 작성하던 관례도 다 이러한 JDK Proxy의 특성때문이기도 하다.

 

CGLib Proxy

  • Target 클래스를 상속받아 프록시를 만든다.
  • JDK 방식과는 달리 인터페이스를 구현하지 않아도 된다.
    • 구체 클래스에 의존하기 때문에 런타임 에러가 발생할 확률도 적다.
  • JDK 방식은 내부적으로 Reflection(동적으로 Class를 Load하고, Heap에 객체를 띄우는 선행 절차가 존재해서 성능이슈가 있다.)을 사용해서 추가적인 비용이 든다.
    • CGLib는 Reflection을 사용하지 않는다.

CGLib Proxy는 Reflection 방식을 사용하지 않고(성능상 이점) 예외를 발생시키지 않는다고 하여 Spring Boot에서는 CGLib를 사용한 방식을 기본으로 채택하고 있다.


Proxy 형태로 동작하는 @Transactional

트랜잭션 처리를 위한 @Transactional 어노테이션은 Spring AOP의 대표적인 예이다.

  • Spring은 JDK Proxy, Spring Boot는 CGLib Proxy를 기본으로 한다.

  1. target 대한 호출이 들어오면 AOP proxy 이를 가로채서(intercept) 가져온다.
  2. AOP proxy에서 Transaction Advisor commit 또는 rollback 등의 트랜잭션 처리를 한다.
  3. 트랜잭션 처리 외에 다른 부가 기능이 있을 경우 해당 Custom Advisor에서 처리를 한다.
  4. Advisor에서 부가 기능 처리를 마치면 Target Method 수행한다.
  5. interceptor chain 따라 caller에게 결과를 다시 전달한다.

@Transactional에 적용시 주의사항

  • private 메서드는 트랜잭션 처리를 할 수 없다.
    • 프록시 객체는 타겟 객체/인터페이스를 상속 받아서 구현하는데, private로 되어 있으면 자식인 프록시 객체에서 호출할 수 없다.
접근제어자 클래스 내부 패키지 상속받은 클래스 이외의 영역
private O X X X
default O O X X
protected O O O X
public O O O O

 

  • 트랜잭션은 외부 객체에서 처음 진입하는 메서드를 기준으로 동작한다.
    • 객체 외부에서 처음 진입하는 메서드를 기준으로 동작한다.
    • 이 사항은 반드시 해당 링크에 있는 예제를 통해서 이해를 해야지 실무에 적용할 때 실수를 안할 수 있다.

결론

  • AOP는 흩어진 관심사를 별도의 클래스오 모듈화 하는 프로그래밍 방법을 말하여, OOP를 더욱 잘 지킬 수 있도록 도움을 준다.
  • Spring AOP는 프록시 객체를 자동으로 생성해주어, Aspect/Advice에 직접적으로 의존하지 않게 해준다.
  • @Transactional도 Spring AOP 중 하나로 프록시 방식으로 동작한다.
    • 원본 클래스/인터페이스를 상속 받아 프록시를 생성하기 때문에 접근 제어자가 private으로 되어 있으면 안된다.
    • 객체 외부에서 처음으로 진입하는 메서드에 트랜잭션 처리가 되어 있어야, 해당 요청을 프록시 객체가 대신 처리할 수 있다.
      따라서 트랜잭션은 객체 외부에서 처음 진입하는 메서드를 기준으로 동작한다.