Notion - Spring Aop와 Proxy
오늘은 Spring의 Proxy에 대해서 알아 볼 예정이다.
추가로 Proxy에 대해 알아보려면 Spring의 AOP에 대해서도 이해하면 좋기 때문에 이도 같이 다룰 예정이다.
Proxy?
Proxy Server
프록시는 어디서 들어본 것 같은 느낌이 많이 드는데, 이는 프록시 서버 떄문일 것같다.
프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 시스템 혹은 프로그램을 뜻한다.
클라이언트와 서비스 사이의 중계기능을 하는 것을 뜻하며, 쉽게 말하자면 중계 서비스, 즉 대리로 동작을 수행하는 것이다.
즉, 프록시 서버는 대리 통신을 해준다고 생각하면 된다.
Spring에서의 Proxy
그렇다면 Spring에서 Proxy는 어떻게 사용될까?
Spring에서도 물론 Proxy는 대리인의 입장으로, 클라이언트와 실제 요청을 처리하는 타깃 사이에서 기능을 수행하는 역할을 한다.
Proxy는 원래 객체를 대신해서 클라이언트의 요청을 받아들이고, 부가기능을 처리하며 타겟을 호출하여 핵심 기능을 실행시키도록 하는 것이다.
이러한 방식으로 동작하는 이유는 Proxy에서 처리하는 부가기능은 중복될 여지가 있기 때문이다.
부가기능은 트랜잭션 관리, 로깅, 보안과 같은 기능들을 말한다.
@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
@Transactional // Proxy에서 관리
public Member getUser(Principal principal) {
log.info("서비스 실행"); // Proxy에서 로깅 실행
Member member = memberRepository.findByUsername(principal.getName()); // 보안 부가기능 실행, 핵심로직 호출
log.info("회원 조회"); // Proxy에서 로깅 실행
return member;
}
}
위 코드의 주석에 설명한 것 처럼 트랜잭션, 로깅, 보안과 같은 부가기능은 Proxy 실행하며 핵심로직은 호출하는 방식으로 진행된다.
위와 같이 Proxy에서 처리하는 방식이 바로 AOP(Aspect-Oriented Programming)에서 사용되며, 로깅, 트랜잭션 관리, 보안 등과 같은 공통의 관심사를 분리해 처리하게 한다.
그렇기에 Spring에서의 Proxy는 클라이언트와 타깃 사이의 중계자 역할뿐만 아니라, 관점 지향 프로그래밍의 핵심 기능을 구현하는 중요한 요소라고 할 수 있다.
Spring AOP
이제 Spring AOP에 대해서 알아보자.
Spring AOP는 Proxy를 기반으로 하여 Runtime Weaving 방식으로 동작한다.
Runtime Weaving은 실행시점에 동적으로 Proxy를 생성하고 이를 통해 부가기능을 적용하는 방식을 말한다.
Spring AOP에서는 두 가지의 Proxy 방식을 사용하고 있다.
JDK Dynamic Proxy
Spring의 기본 동작방식인 JDK Dynamic Proxy이다.
- JDK Dynamic Proxy는 Proxy Factory에 의해서 런타임 시 동적으로 Proxy 객체를 생성하게 된다.
- 이는 인터페이스에 대한 명세를 기준으로 Proxy를 생성하게 되므로 반드시 인터페이스를 정의해야만한다는 단점이 있다.
- 동적으로 생성된 Proxy 객체의 경우 InvocationHandler 인터페이스를 상속받아 구현되게 되며, Invoke 함수를 통해 부가기능을 실행하게 된다.
- 이 과정에서 Reflection을 이용하는데, Reflection은 일반적인 메서드 호출 보다 오버헤드가 크고 JVM이 최적화 할 수 없는 이슈가 있기 때문에 성능 저하가 발생할 수 있다.
CGLib Proxy
Spring boot의 기본 동작 방식인 CGLib Proxy이다.
- CGLib Proxy는 CGLib 라이브러리를 통해 타겟 클래스를 상속받아 Proxy 객체를 생성한다.
- 그렇기 때문에 인터페이스를 만들지 않아도 되지만, 구현 클래스를 상속받아 사용하기에 private이나 final 같이 상속을 지원하지 않는 클래스나 메서드를 만들 수 없다는 단점이 있다.
- CGLib Proxy는 바이트 코드를 조작해서 프록시 객체를 생성하기 때문에 JDK Dynamic Proxy 보다 성능이 좋다는 장점도 있다.
Spring boot에서 CGLib Proxy를 선택한 이유
객체지향 관점에서 봤을 때, 인터페이스를 활용해서 DI를 적극적으로 사용할 수 있는 JDK Dynamic Proxy를 쓰지 않은 이유가 있을까 싶었다.
이는 JDK Dynamic Proxy를 사용했을 때 발생하는 문제와 대체재가 있었기 때문이다.
- 빈 주입 시 발생하는
ClassCastException
- JDK Dynamic Proxy를 사용할 시 인터페이스 구현은 필수이다.
- @Bean을 통해 구현한 인터페이스에 대한 구현 클래스를 주입할 때, 반환 타입이 인터페이스인 경우에 Spring은 JDK Dynamic Proxy를 통해 생성된 프록시 객체를 빈으로 등록하게 된다.
- 이는 트랜잭션같은 공통 관심사를 분리하기 위함이다.
- 그렇기에 기존 구현 클래스를 사용하려 할 때, 실제 주입된 클래스는 JDK Dynamic Proxy를 통해 생성된 프록시 객체이기 때문에
ClassCastException
이 발생하게 된다.
- CGLIB 의존성 해결
- Spring Framework 3.2 버전부터 CGLIB의 모든 클래스를 포함하고 있는 JAR 파일을 가지게 되었기에 CGLIB에 대한 의존성 문제는 더 이상 고려대상이 아니게 되었다.
위와 같은 이유들 때문에 Spring boot 2.0 버전 부터는 기본 프록시 생성 전략을 CGLib Proxy로 변경하게 되었다.
AOP 주요 개념 정리
- Aspect
- 여러 코드에서 공통으로 사용되는 기능을 모듈화한 것을 의미한다.
- 이는 '부가기능’이라고도 불리며, 로깅, 트랜잭션 관리 등과 같은 공통 기능을 별도의 모듈로 분리하여 관리한다.
- Target
- Aspect가 적용될 대상을 가리킨다.
- 이는 주로 비즈니스 로직, 핵심 로직을 담고 있는 구현체를 의미한다.
- Advice
- 실질적으로 실행될 부가기능을 담고 있는 구현체를 의미한다.
- 이는 Aspect의 구체적인 동작을 정의하며, 언제 어떤 작업을 수행할지를 결정한다.
- JointPoint
- Advice가 적용될 위치를 가리킨다.
- 이는 부가기능이 실행될 시점이나 위치, 또는 핵심 로직이 호출될 위치를 의미한다.
- PointCut
- JointPoint의 상세한 내용을 정의한다.
- 이를 통해 Advice가 실행될 지점을 더욱 구체적으로 지정할 수 있다.
- 특정 메서드 호출, 예외 발생 등과 같은 프로그램의 실행 흐름에서 특정 지점을 가리킨다.
참고
'Spring' 카테고리의 다른 글
Spring Security 6 - Authentication Persistence and Session Management (0) | 2024.06.22 |
---|---|
[Spring] @Transactional 에 대해서 (1) | 2024.04.24 |
[Spring] HTTP 메시지 컨버터 (0) | 2022.05.23 |
[SpringMVC] HTTP 요청, 응답처리 (0) | 2022.05.23 |
[SpringMVC] 매핑 (Mapping) (0) | 2022.05.23 |