Spring Bean은 Proxy로 만들어질까?
- 일반 빈을 조회하였을 때
- OrderService 및 빈 등록
public class OrderService { Map<Long, Order> orders = new HashMap<>(); public Order saveOrder(Order order) { orders.put(order.getId(), order); return orders.get(order.getId()); } }
@Configuration public class OrderConfig { @Bean public OrderService orderService() { return new OrderService(); } }
- Bean 조회 후 확인
@Test void getOrderServiceBeanTest() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(OrderConfig.class); OrderService orderService = (OrderService) context.getBean("orderService"); Assertions.assertEquals(orderService.getClass(), OrderService.class); }
- Spring Context를 생성하여 OrderService 확인해본 결과 Proxy 객체가 아닌 순수 객체각 반환되는 것을 확인할 수 있다.
- OrderService 및 빈 등록
- AOP를 적용한 빈을 조회하였을 때
- AopServiceAspect 및 AopService, AopConfig
@Aspect @Slf4j @Configuration public class AopServiceAspect { @Around(value = "execution(* com.example.bean.AopService.incrementCount())") public void aroundService(ProceedingJoinPoint joinPoint) { log.info("START SERVICE"); try { joinPoint.proceed(); } catch (Throwable e) { log.error("Around exception: {}", e.getMessage()); } log.info("END SERVICE"); } }
public class AopService { private int count = 0; public int incrementCount() { return ++count; } }
@Configuration @EnableAspectJAutoProxy public class AopConfig { @Bean public AopService aopService() { return new AopService(); } @Bean public AopServiceAspect aopServiceAspect() { return new AopServiceAspect(); } }
- 빈 등록 확인
@Test void getAopServiceBeanTest() throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); AopService aopService = (AopService) context.getBean("aopService"); Assertions.assertNotEquals(aopService.getClass(), AopService.class); }
- AOP를 적용한 빈을 조회하였을 때 Proxy로 생성되어져 있는 것을 확인할 수 있다.
- AopServiceAspect 및 AopService, AopConfig
- 빈을 등록하는 과정에서 AOP 여부를 확인한다
- Spring Context 가 Bean 후보를 스캔하여 생성할 때, 빈이 생성되는 과정에서 AbstractAutoProxyCreator.wrapIfNeccesary() 함수가 호출되는 데, Proxy로 Wrap 하는 역할을 수행
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // validation... // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
- getAdvicesAndAdvisorsForBean 은 추상메소드로 AbstractAdvisorAutoProxyCreator에 구현
- specificInterceptors에 따라 Proxy 클래스를 생성하여 리턴한다
- 이 뿐만 아니라 Spring은 Bean의 관계를 파악하고 Proxy로 등록해야할 지를 결정하는 것으로 보인다.
- Proxy 객체를 만들 수 있는 이유
- CGLIB Proxy는 빈과 그 빈을 감싸는 프록시 객체는 부모-자식 관계로 주입을 받을 수 있다.
- 만약 빈을 등록할 때 final class로 상속을 더이상 못하게 한다면 프록시 객체를 생성할 수 없기 때문에 오류가 발생한다.
Could not generate CGLIB subclass of class com.example.bean.AopServic
- Spring Context 가 Bean 후보를 스캔하여 생성할 때, 빈이 생성되는 과정에서 AbstractAutoProxyCreator.wrapIfNeccesary() 함수가 호출되는 데, Proxy로 Wrap 하는 역할을 수행
- CGLIB vs jdk dynamic proxy
- jdk dynamic proxy
- 1.3 버전부터 생긴 기능으로 interface 기반으로 Proxy 생성
- invocation handler를 상속받아 실체를 구현하는데, invoke 메소드(reflection)을 활용하기 때문에 성능이 떨어진다.
- MethodMatcher
- CGLIB
- CGLIB은 reflection을 사용하지 않고 상속을 사용하여 Proxy할 메서드를 오버라이딩하는 방식
- MethodInterceptor
- jdk dynamic proxy