이 글은 바로 전 포스팅에서 설명하는 proxy에 대한 자세한 설명을 하기 위한 포스팅임을 밝힙니다! @Transactional을 사용할 때 주의 사항에 대해 알고 싶으신 분은 다음 링크를 참조해주세요!
https://velog.io/@sleepyhoon/Transactional-%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC
proxy 객체는 원래 객체를 감싸고 있는 객체로, 원래 객체와 타입은 동일하지만 2가지 차이점이 존재한다.
Spring에서는 크게 2가지 proxy 구현체를 사용한다. JDK proxy(동적 proxy)와 CGLib이다.
이때 Spring AOP는 proxy을 기반으로 작동한다.

다음 그림처럼 Spring AOP는 사용자의 call 시점에 IoC 컨테이너에 의해 AOP를 할 수 있는 Proxy Bean을 생성한다. 동적으로 생성된 Proxy Bean은 타깃의 메서드가 호출되는 시점에 부가기능을 추가할 메서드를 자체적으로 판단해 가로채어 부가기능을 주입한다. 이를 호출시점에 동적으로 위빙을 한다해 런타임 위빙(Runtime Weaving)이라고 한다.
Spring AOP는 런타임 위빙 방식을 기반으로 하고 있고, Spring 에서는 상황에 따라 JDK Proxy와 CGLib방식을 통해 Proxy Bean을 생성해준다.

자체 검증 로직을 통해 타겟의 인터페이스 유무를 판단하여 결정한다.
JDK Proxy는 java의 reflection의 Proxy 클래스가 동적으로 Proxy를 생성해준다. 이 클래스를 사용하기 위한 몇 가지 조건이 있지만 핵심은 타겟의 인터페이스를 기준으로 Proxy를 생성해준다는 점이다.

JDK Proxy가 Proxy 객체를 생성하는 방식은 다음과 같다.
ProxyFactory에 의해 타겟의 인터페이스를 상속한 Proxy 객체를 생성한다.Proxy 객체에 InvocationHandler를 포함시켜서 하나의 객체로 반환한다.다음과 같이 Proxy를 생성하는 과정에서 핵심적인 부분은 인터페이스를 기준으로 Proxy객체를 생성한다는 것이다. 따라서 구현체는 인터페이스를 상속해야 하고, @Autowired을 통해 생성된 Proxy Bean을 사용하기 위해서는 반드시 인터페이스의 타입으로 지정해야 한다. 이런 Proxy의 구조를 이해하지 못하면 다음과 같은 실수를 저지를 수 있다. (만약 JDK Dynamic Proxy만 사용한다고 가정했을 때)
@Controller
public class UserController{
@Autowired
private UserServiceImpl memberService; // <- Runtime Error 발생...(Interface가 아닌 Class 타입으로 DI를 헀다)
...
}
@Service
public class UserServiceImpl implements UserService{
@Override
public Map<String, Object> findUserId(Map<String, Object> params){
...isLogic
return params;
}
}
MemberService는 인터페이스를 상속받고 있기 때문에 Spring은 JDK Proxy 방식으로 Proxy Bean을 생성한다. 그렇기 때문에 위 코드를 실행하면 RuntimeException이 발생한다. 따라서
private UserServiceImpl userService -> private UserService userSerivce로 바꿔줘야한다.
CGLib은 클래스의 바이트 코드를 조작해 Proxy 객체를 생성해주는 라이브러리이다. Spring은 CGLib을 사용해 인터페이스가 아닌 클래스에 대해서도 Proxy를 생성해준다. CGLib는 Enhancer라는 클래스를 통해 Proxy를 생성할 수 있다.
@Test
@DisplayName("CGLib test")
void test1() throws Exception {
MockService mockService = new MockService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MockService.class);
enhancer.setCallback(new SubLogicInterceptor(mockService));
MockService proxy = (MockService) enhancer.create();
proxy.logic1();
}
이 과정에서 CGLib는 타겟 클래스에 포함된 모든 메서드를 제정의해서 Proxy를 생성해준다. 이 때문에 CGLib은 final 메서드 or 클래스는 재정의를 할 수 없기에 Proxy를 생성할 수 없다는 단점이 있지만, CGLib은 바이트 코드를 조작해서 Proxy를 생성하기 때문에 JDK Proxy보다 성능이 좋다. 성능 차이의 근본적인 이유는 CGLib은 타겟에 대한 정보를 제공받기 때문이다.
Spring 에서 Proxy 객체를 생성할 때 JDK Dynamic Proxy, CGLib Proxy 중 한 가지 방식을 사용한다. 그것을 구분하기 위해서
1. 인터페이스를 구현하고 있는지 체크한다.
2. 구현하고 있다면 JDK Proxy 사용한다.
3. 구현하지 않는다면 CGLib 사용한다.
Spring Boot 2.0부터
proxy-target-class=true설정을 추가하면 CGLIB 프록시가 기본값이라고 한다.