새소식

Tech Interview/Interview

3. 질문 (Proxy & SpringBoot)

  • -

Proxy : 일종의 대리자. 개발할 때 특정한 Interface를 노출시키지 않고, 외부로부터 감추고 싶을 때 사용하는 것이 바로 Proxy 패턴입니다. 하지만 Spring에서 지원하는 Proxy와 디자인 패턴에서의 Proxy 패턴은 다릅니다. 일반적으로 Proxy는 실제 Target의 기능을 대신 수행하면서, 기능을 확장하거나 추가하는 실제 객체를 의미한다. Proxy 패턴은 Target에 대한 기능을 확장하지는 않고, Client가 Target에 접근하는 방식을 변경해준다. Proxy를 사용하는 이유는 OCP, SRP를 지키키 위해서 입니다. 프록시 패턴을 통해 프록시 코드를 구현할 수 있다. 쉽게 말해 1) 클라이언트가 타깃에 접근하는 방법을 제어할 때 예로 JPA의 지연로딩 2) 타깃에 부가적인 기능을 부여할 때 예로 트랜잭션 같은 것.

 

단점 : 코드 복잡도가 증가한다. 왜? 인터페이스와 프록시 객체를 추가 생성해야하기 때문에 + 중복 코드가 발생할 수 있다.

이를 동적 프록시를 통해 해결할 수 있다. 프록시 클래스를 직접 구현하지 않아도되어 위 단점인 코드 복잡도를 해소할 수 있고, Invocation Handler를 통해 중복코드를 제거할 수 있다.

 

 

JDK Dynamic Proxy

ex) client (메서드 요청) -> JDK Dynamic Proxy (메서드 처리) -> Invocation Handler (부가기능 수행) -> 타겟

Spring 에서는 Proxy를 바탕으로 우리의 관심사를 추출하는 AOP를 제공합니다.

 

1) JDK에서 지원하는 프록시 생성 방법

2) reflection API를 사용 -> 구체적인 클래스 타입을 알지 못해도 런타임에 클래스의 정보에 접근할 수 있게 해주는 자바 API. 그러나 리플렉션은 동적일 때 해결되는 타입을 포함하므로 JVM의 optimization이 작동하지 않아 성능상 느리다.

3) 인터페이스가 반드시 있어야 한다.

4) Invocation Handler를 재정의한 invoke를 구현해여 부가기능이 추가된다.

 

CHLIB

1) 상속을 통한 프록시 구현

2) 바이트 코드를 조작해서 프록시를 생성

3) MethodIntercepter를 재정의한 intercept을 구현해야 부가기능이 추가된다.

4) net.sf.cglib.proxy.Enhancer 의존성을 추가해야 한다.

5) 타겟의 생성자 2번 호출

6) 메서드에 final을 붙이면 오버라이딩이 불가능하다.

7) 인터페이스에도 강제로 적용할 수 있다. 이때는 클래스에도 프록시를 적용시켜야한다.

 

간단 요약 차이점

JDK 다이나믹 프록시는 Reflection을 사용해 느리다. 인터페이스가 있으면 작동한다.

CGLIB는 바이트 코드를 조작해서 빠르다. 클래스만 있어도 작동한다.

 

AOP가 관심사 별로 묶는 것은 아는데, 어떻게 추출할까요? -> 바로 Proxy를 이용한 런타임 위빙을 통해서 관심사를 추출합니다. 

Runtime Weaving? Weaving은 target 객체를 새로운 proxied 객체로 적용시키는 과정이고, 이를 Runtime시에 진행한다 이다.

 

Spring AOP에서는 이러한 기능을 JDK Dynamic ProxyCGLib Proxy 방법으로 구사합니다.

JDK Dynamic Proxy : JDK에서 제공하는 Dynamic Proxy는 1.3 버전부터 생긴 기능이며, Interface를 기반으로 Proxy를 생성해주는 방식입니다. 그렇기 때문에 Interface를 강제화한다는 단점이 있습니다. Dynamic Proxy는 Invocation Handler를 상속받아서 실체를 구현하게 되는데, 이 과정에서 특정 Object에 대해 Reflection을 사용하기 때문에 성능이 조금 떨어지는 치명적인 단점이 있습니다.

 

 

 

CGLib : 클래스의 바이트 코드를 조작하여 프록시 객체를 생성해주는 라이브러리.

CGLib Proxy : Enhancer를 바탕으로 Proxy를 구현하는 방식입니다. 이는 Reflection을 사용하지 않고, Extends 상속 방식을 이용해서 Proxy화 할 메서드를 오버라이딩 하는 방식입니다.

 

먼저 Proxy화를 진행할 Target Class를 생성하고, 간단하게 start와 stop메서드를 통해 메서드를 나타냅니다. 그리고 start 메서드만 Proxy화를 진행한다면,

그래서 MethodMatcher라는 인터페이스를 선언하고 상속해서 특정 조건에 의해 필터링합니다. 그리고 실제 Proxy로 핸들링 할 Handler가 필요한데 CGlib에서는 이를 MethodIntercepter 라는 인터페이스로 정의되어있습니다.

Intercept 라는 메서드가 오버라이딩 해서, Proxy 로직을 진행하게 됩니다. 테스트를 해보면??

정상적으로 Proxied 된 객체로 실행되는 것을 볼 수 있습니다. 여기서 start 메서드만 proxy화 하게 설정하였고, 이를 테스트하는 코드입니다. 코드에서도 볼 수 있듯 Enhancer 객체는 반드시 부모클래스를 지정해야 합니다. CallBack을 통해서 어떠한 Handler를 설정할 것인지 바로 명시해야 하죠.

 

CGlib는 기본적으로 Byte 코드를 조작해서, 바이너리가 만들어지기 때문에 JDK Dynamic Proxy 보다 성능적으로 우세합니다. 다만 final 객체 혹은 private 접근자로 된 메서드는 상속이 지원되지 않기 때문에 제약적인 Proxy 구현이 가능하다는 것을 알 수 있습니다.

 

위의 코드는 AOP의 근본이 되는 코드입니다.

위 코드에서 예제로 이용한 코드를 Spring AOP에 접목시켜보면

 

JDK Dynamic Proxy에서 InvocationHandler, CGlib 에서 MethodInterceptor 

Spring AOP에서 JoinPoint 라는 개념과 일치합니다

 

그리고, 위에서 특정 조건에 의해 필터링 하는 MethodMatcher 

Spring AOP에서 PointCut 라는 개념과 일치합니다

 

마지막으로 Proxy 로직이 실행되는 JDK Dynamic Proxy에 invoke 메서드, CGlib 에서 Intercept 메서드는

Spring AOP에서 Advice 라는 개념과 일치합니다

 

물론 실제로 Spring AOP를 활용한다고, 하나부터 열까지 다 직접 구현하지는 않습니다

 

실제 현업에서는 @AspectJ(Class, Method 단위) 어노테이션과,

어떻게 Advice를 지정할 것인가에 대한 @Before, @Around, @AfterThrowing ... 특정 조건을 필터링할 Expression 을 기반으로 PointCut 설정들을 편하게 하는 것이 이미 만들어져있으니까요~

 

 

보일러 플레이트 : 모든 코드를 작성하기 위해 항상 필요한 부분을 의미

 

Spring Boot : 스프링 프레임워크에서 설정했던 모든 정보들을 간편하게 설정할 수 있도록 경량화되어 제공한 프레임워크이다.

개발자가 개발에 집중하여 스프링 프레임워크에서 작성하던 많은 설정들을 패키징하여 간편하게 제공할 수 있도록 함.

 

@SpringBootApplication 내부에는

1) @SpringBootConfiguration : 빈에 대해 Context에 추가하거나 특정 클래스를 참조해 올 수 있다.

2) @ComponentScan : 패키지 내 application 컴포넌트가 어디에 위치해 있는지 검사한다.

3) @EnableAutoConfiguration : Spring Boot의 자동화 기능(Spring 설정)을 활성화 시켜준다.

 

AutoConfiguration -> 이전엔 JDBC Template을 사용하려면 JDBC Template을 스프링 빈으로 선언하고 DataSource를 설정했어야 했다. 스프링부트는 자동으로 인메모리 데이터 베이스와 JDBC Template을 구성해준다. 이것은 하나의 예시이고, 자동설정은 200개 이상의 설정을 해결해준다.

 

Spring-boot-starter -> 스프링 프레임워크에서 사용하던 다양한 플러그인은 서로 간 의존성 충돌되는 이슈가 번번히 발생했다. 플러그인을 버전관리 하는데 많은 노력이 필요했는데, Spring Boot에서는 Starter 플러그인을 사용하여 패키지된 플러그인으로 제공되어 상호간에 의존성 충돌에 대해 노력을 최대한 줄일 수 있게 되었다.

 

내장형 톰켓 -> 스프링 프레임워크에서는 별도로 WAS를 실행하여 애플리케이션의 빌드된 파일 경로를 지정해주어 실행했는데, Spring Boot에서는 WAS가 내장되어 애플리케이션 실행시 자동으로 실행될 수 있다.

 

@Transactional -> 스프링에서 트랜잭션 처리는 보통 트랜잭션 어노테이션을 많이 사용하는데, 클래스와 메소드에 사용할 수 있으며 @Tracnsactional이 포함된 메서드가 호출되면 프록시 객체가 생성된다. 프록시 객체는 해당 메소드 실행 이전에  PlatformTransactionalManager를 사용하여 트랙잭션을 시작하고 결과에 따라 commit 또는 Rollback 한다.

 

Commit은 CheckedException or 예외가 없을때, Rollback은 UncheckException이 발생하면 생긴다.

@Transactional 우선순위는 -> 1) 클래스 메서드 2) 클래스 3) 인터페이스 메서드 4) 인터페이스

 

단 주의해야 할 부분이 있다. @Transactional 어노테이션은 Spring AOP를 이용하게 되는데 이 AOP는 기본적으로 Dynamic Proxy를 이용한다. Dynamic Proxy는 인터페이스 기반으로 동작하기 때문에 인터페이스가 없을경우 트랜잭션이 동작하지 않는다. 인터페이스 없이 트랜잭션 동작하게 하려면 CGLib(Code Generation Library) Proxy를 이용하면 된다. CGLib Proxy는 클래스에 대한 Proxy가 가능하기 때문에 인터페이스가 없어도 된다.

 

'Tech Interview > Interview' 카테고리의 다른 글

5. 질문 (웹 브라우저에 URL 전체 동작과정)  (0) 2024.03.09
4. 질문 (Middleware)  (0) 2023.12.06
1. JPA 소개 & 영속성 관리  (1) 2023.11.30
2. 질문  (0) 2023.11.27
1. 질문  (0) 2022.07.20
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.