Backend/🌿 Spring

[Spring] ProxyFactory

Hugehoo 2022. 6. 3. 11:01

 

이전 ν¬μŠ€νŒ…μ—μ„œ 동적 ν”„λ‘μ‹œλ₯Ό μ μš©ν•  λ•Œμ˜ λ¬Έμ œμ μ„ μ§šμ–΄λ΄€λ‹€.

 

  1. μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†λŠ” κ²½μš°μ—λŠ” CGLIBλ₯Ό, μžˆλŠ” κ²½μš°μ—λŠ” JDK 동적 ν”„λ‘μ‹œλ₯Ό μ μš©ν•΄μ•Όν•œλ‹€.
  2. 두 κΈ°μˆ μ„ λͺ¨λ‘ μ‚¬μš©ν•˜λ €λ©΄, JDK 동적 ν”„λ‘μ‹œμ˜ InvocationHandler 와 CGLIB의 MethodInterceptorλ₯Ό μ€‘λ³΅ν•΄μ„œ λ§Œλ“€μ–΄ κ΄€λ¦¬ν•΄μ•Όν• κΉŒ?
  3. νŠΉμ • μ‘°κ±΄μ—μ„œ(ν•„ν„°) ν”„λ‘μ‹œ λ‘œμ§μ„ μ μš©ν•˜λŠ” κΈ°λŠ₯을 κ³΅ν†΅μœΌλ‘œ μ μš©ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€. 

 

μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” μΆ”μƒν™”λœ ν”„λ‘μ‹œ 기술 : ProxyFactory 

μŠ€ν”„λ§μ€ 동적 ν”„λ‘μ‹œλ₯Ό ν†΅ν•©ν•˜μ—¬ 생성해 μ£ΌλŠ” ν”„λ‘μ‹œ νŒ©ν† λ¦¬(Proxy Factory) λΌλŠ” κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€. μ΄μ „μ—λŠ” μΈν„°νŽ˜μ΄μŠ€μ˜ μœ λ¬΄μ— 따라 JDK 동적 ν”„λ‘μ‹œλ‚˜ CGLIB 을 선택해야 ν–ˆλ‹€λ©΄, μ΄μ œλŠ” Proxy Factory ν•˜λ‚˜λ‘œ  νŽΈλ¦¬ν•˜κ²Œ 동적 ν”„λ‘μ‹œλ₯Ό 생성할 수 μžˆλ‹€. Proxy Factory 의 의쑴 관계와 μ‚¬μš© 흐름은 μ•„λž˜ κ·Έλ¦Όκ³Ό κ°™λ‹€. 

 

Advice , Pointcut λ„μž…

μŠ€ν”„λ§μ€ Advice λΌλŠ” μƒˆλ‘œμš΄ κ°œλ…μ„ λ„μž…ν•˜λ©΄μ„œ, 상황에 따라 JDK λ™μ ν”„λ‘μ‹œλ‚˜ CGLIB λ₯Ό λ”°λ‘œ μƒμ„±ν•΄μ•Όν•˜λŠ” 문제λ₯Ό ν•΄κ²°ν–ˆλ‹€. κ°œλ°œμžλŠ” InvocationHandler λ‚˜ MethodInterceptor λ₯Ό 신경쓰지 μ•Šκ³ , Advice 만 μƒμ„±ν•˜λ©΄ λœλ‹€. ( Advice λŠ” κ°œλ°œμžκ°€ μ μš©ν•˜κ³  싢은 λΆ€κ°€ κΈ°λŠ₯ 둜직이라 μƒκ°ν•˜μž )

ν”„λ‘μ‹œ νŒ©ν† λ¦¬λ₯Ό μ‚¬μš©ν•˜λ©΄, λ‚΄λΆ€μ—μ„œ Advice λ₯Ό ν˜ΈμΆœν•˜λŠ” μ „μš© InvocationHandler, MethodInterceptor λ₯Ό λ‚΄λΆ€μ—μ„œ μ‚¬μš©ν•œλ‹€.

이전에 FilterHandler λ₯Ό λ§Œλ“€μ–΄, νŠΉμ • 쑰건에 λ§žλŠ” λ©”μ„œλ“œμ—λ§Œ ν”„λ‘μ‹œλ₯Ό μ μš©ν•œ 적이 μžˆλ‹€. ν”„λ‘μ‹œ νŒ©ν† λ¦¬μ—μ„œλŠ” Pointcut μ΄λΌλŠ” κ°œλ…μ„ λ„μž…ν•˜μ—¬ 이 문제λ₯Ό 일관성 있게 ν•΄κ²°ν•œλ‹€. 예제 μ½”λ“œλ₯Ό λ³΄λ©΄μ„œ Advice 와 Pointcut 을 더 μ΄ν•΄ν•΄λ³΄μž. 

 

 

 

Advice, Pointcut 예제 μ½”λ“œ

Advice λŠ” λΆ€κ°€κΈ°λŠ₯ λ‘œμ§μ„ λ‹΄λ‹Ήν•œλ‹€κ³  ν–ˆλ‹€. μ—¬κΈ°μ„œ TimeAdvice λŠ” λ©”μ†Œλ“œμ˜ μ‹€ν–‰ μ‹œκ°„μ„ μ²΄ν¬ν•˜μ—¬ 둜그λ₯Ό λ‚¨κΈ°λŠ” λΆ€κ°€κΈ°λŠ₯을 λ‹΄λ‹Ήν•œλ‹€. Advice λŠ” MethodInterceptor λ₯Ό 상속받아 invoke(MethodInvocation invocation) λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•˜λŠ”λ°, 이 λ•Œ invocation.proceed() λ₯Ό ν˜ΈμΆœν•˜λ©΄ targe 클래슀λ₯Ό ν˜ΈμΆœν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό λ¦¬ν„΄λ°›λŠ”λ‹€. 그런데 이전 ν¬μŠ€νŒ…κ³Ό 달리 target 의 정보가 보이지 μ•ŠλŠ”λ‹€. μ΄μ „μ—λŠ” μƒμ„±μžλ‘œ target 을 μ£Όμž…λ°›μ•˜μ§€λ§Œ, MethodInterceptor λ₯Ό μ‚¬μš©ν•˜λ©΄, MethodInvocation invocation 내뢀에 target 의 클래슀 정보가 λͺ¨λ‘ 포함돼 μžˆλ‹€.

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@Slf4j
public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("TimeProxy μ‹€ν–‰");
        long startTime = System.currentTimeMillis();
        
        // κΈ°μ‘΄
        //Object result = method.invoke(target, args); 

        // λ³€κ²½ : MethodInterceptor 상속 ν›„ 
        Object result = invocation.proceed(); 
        // target 을 μžλ™μœΌλ‘œ μ°Ύκ³  인자λ₯Ό λ„˜κΈ΄λ‹€. -> μ‹€μ œ νƒ€κ²Ÿμ„ ν˜ΈμΆœν•œλ‹€.

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeProxy μ’…λ£Œ resultTime={}", resultTime);
        return result;
    }
}

μ£Όμ˜ν•  점은, TimeAdvice κ°€ μƒμ†λ°›λŠ” MethodInterceptor κ°€ CGLIB의 MethodInterceptor 와 이름이 κ°™μœΌλ―€λ‘œ νŒ¨ν‚€μ§€ 이름에 μ£Όμ˜ν•΄μ•Όν•œλ‹€. Advice λ₯Ό μ μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” org.aopalliance.intercept μŠ€ν”„λ§ AOP λͺ¨λ“ˆ 내뢀에 μžˆλŠ” 것을 μ‚¬μš©ν•œλ‹€.

이제 μš°λ¦¬κ°€ λ§Œλ“  Advice λ₯Ό ProxyFactory 에 μ „λ‹¬ν•˜λ©΄ proxy κ°€ λ°˜ν™˜λ˜κ³  μ μš©λ˜λŠ” 것을 확인할 수 μžˆλ‹€.

 

Advice - ProxyFactory 적용  (1) : μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†λ°›λŠ” target 의 proxy 생성

  • μš°μ„  Proxy λ₯Ό μ μš©ν•  λŒ€μƒ (target) 객체λ₯Ό λΆˆλŸ¬μ™€μ•Όν•œλ‹€. μ—¬κΈ°μ„œλŠ” ServiceInterface νƒ€μž…μ˜ ServiceImpl 의 μΈμŠ€ν„΄μŠ€λ₯Ό target 으둜 μ§€μ •ν–ˆλ‹€. ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό 상속받고 μžˆμŒμ„ μΈμ§€ν•˜μž
  • κ·Έ λ‹€μŒ ProxyFactory() μΈμŠ€ν„΄μŠ€λ₯Ό 생성할 λ•Œ 인자둜 target 객체λ₯Ό λ„˜κ²¨μ€€λ‹€. ν”„λ‘μ‹œ νŒ©ν† λ¦¬λŠ” 이 μΈμŠ€ν„΄μŠ€ 정보λ₯Ό 기반으둜 λ‚΄λΆ€μ—μ„œ ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•œλ‹€. 
  • μš°λ¦¬κ°€ λ„˜κ²¨μ€€ μΈμŠ€ν„΄μŠ€λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†λ°›μ•˜λ‹€κ³  μ–ΈκΈ‰ν–ˆλ‹€. 즉 target μΈμŠ€ν„΄μŠ€μ— μΈν„°νŽ˜μ΄μŠ€κ°€ μžˆλ‹€λ©΄ ProxyFactory λ‚΄λΆ€μ—μ„œ JDK 동적 ν”„λ‘μ‹œλ₯Ό 기본으둜 μ‚¬μš©ν•˜κ³ , 그렇지 μ•Šλ‹€λ©΄ CGLIB λ₯Ό 톡해 동적 ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•œλ‹€. 
  • proxyFactory.addAdvice() : 이제 ν”„λ‘μ‹œκ°€ μ‚¬μš©ν•  λΆ€κ°€κΈ°λŠ₯ λ‘œμ§μ„ addAdvice() 의 인자둜 λ„˜κ²¨μ€€λ‹€. μš°λ¦¬κ°€ λ§Œλ“  TimeAdvice 의 μΈμŠ€ν„΄μŠ€λ₯Ό λ„˜κ²¨μ£Όμž.  ( JDK 동적 ν”„λ‘μ‹œκ°€ μ œκ³΅ν•˜λŠ” InvocationHandler 와 CGLIBκ°€ μ œκ³΅ν•˜λŠ” MethodInterceptor의 κ°œλ…κ³Ό μœ μ‚¬)
  • λΆ€κ°€κΈ°λŠ₯ κΉŒμ§€ 섀정을 마친 proxyFactory λŠ” proxyFactory.getProxy() 둜 ν”„λ‘μ‹œ 객체λ₯Ό μƒμ„±ν•˜κ³  κ²°κ³Όλ₯Ό λ°›λŠ”λ‹€. 
@Slf4j
public class ProxyFactoryTest {

    @Test
    @DisplayName("μΈν„°νŽ˜μ΄μŠ€κ°€ 있으면 JDK 동적 ν”„λ‘μ‹œ μ‚¬μš©")
    void interfaceProxy() {
    
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);  
        // target 을 λ„£μœΌλ©΄μ„œ ν”„λ‘μ‹œ νŒ©ν† λ¦¬λ₯Ό λ§Œλ“ λ‹€. 즉 ν”„λ‘μ‹œ νŒ©ν† λ¦¬λŠ” 이미 νƒ€κ²Ÿμ˜ 정보λ₯Ό μ•Œκ³ μžˆλ‹€.
        // ν”„λ‘μ‹œμ˜ ν˜ΈμΆœλŒ€μƒ(target)을 ν”„λ‘μ‹œ νŒ©ν† λ¦¬λ₯Ό 생성할 λ•Œ 인자둜 λ„˜κΈ°λŠ” 것.

        proxyFactory.addAdvice(new TimeAdvice());  // advice μΆ”κ°€ -> ν”„λ‘μ‹œκ°€ μ‚¬μš©ν•  λΆ€κ°€ λ‘œμ§μ„ μ„€μ •ν•œλ‹€. (advice : 쑰언을 ν•΄μ€€λ‹€.)
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
        
        proxy.save(); // ν”„λ‘μ‹œ 객체의 자체 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄, advice 둜 μΆ”κ°€λœ (둜그) 둜직이 μˆ˜ν–‰λœλ‹€.
        // save() 에 μ •μ˜λœ "save 호좜" μ΄λž€ λ‘œκ·Έκ°€ λ°”λ‘œ μ°νžˆλŠ” 것이 μ•„λ‹ˆλΌ, TimeAdvice() λ‚΄λΆ€μ—μ„œ invocation.proceed() ν•  λ•Œ save()의 λ‘œκ·Έκ°€ μ°νžŒλ‹€,.
        
        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();  // μΈν„°νŽ˜μ΄μŠ€κ°€ μ‘΄μž¬ν•˜λ―€λ‘œ JDK dynamic proxy
        assertThat(AopUtils.isCglibProxy(proxy)).isFalse();

    }
}

 

μ‹€ν–‰ κ²°κ³Ό

 

 

Advice - ProxyFactory 적용  (2) : ꡬ체 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” ν”„λ‘μ‹œ 생성

(1) 예제 μ½”λ“œμ™€ 크게 달라진 것은 μ—†λ‹€. 차이점이라면 μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†λŠ” ꡬ체클래슀λ₯Ό μ‚¬μš©ν–ˆλ‹€λŠ” 점이닀. λ˜‘κ°™μ΄ ν”„λ‘μ‹œ νŒ©ν† λ¦¬λ₯Ό μ‚¬μš©ν•΄ ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•œ ν›„ , ν…ŒμŠ€νŠΈμ½”λ“œλ₯Ό 톡해 μ–΄λ–€ κ²°κ³Όκ°€ λ‚˜μ˜€λŠ”μ§€ ν™•μΈν•΄λ³΄μž. 

@Slf4j
public class ProxyFactoryTest {

    @Test
    @DisplayName("ꡬ체 클래슀만 있으면 CGLIB μ‚¬μš©")
    void concreteProxy() {
        ConcreteService target = new ConcreteService();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.addAdvice(new TimeAdvice());
        ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());

        proxy.call();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
        assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
    }

}

assertThat(AopUtils.isCglibProxy(proxy)).isTrue() κ°€ ν†΅κ³Όν•˜λŠ” 것을 보아 CGLIB λ₯Ό ν™œμš©ν•΄ ν”„λ‘μ‹œκ°€ μƒμ„±λμŒμ„ 확인할 수 μžˆλ‹€.

 

 

Advice - ProxyFactory 적용  (3) : ProxyTargetClass μ˜΅μ…˜ μ‚¬μš©

target μΈμŠ€ν„΄μŠ€λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ†λ°›μ§€λ§Œ, setProxyTargetClass(true) μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©΄ JDK 동적 ν”„λ‘μ‹œ λŒ€μ‹  κ°•μ œλ‘œ CGLIB λ₯Ό μ‚¬μš©ν•΄ ν”„λ‘μ‹œλ₯Ό 생성할 수 μžˆλ‹€.

 

@Slf4j
public class ProxyFactoryTest {

    @Test
    @DisplayName("ProxyTargetClass μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©΄ μΈν„°νŽ˜μ΄μŠ€κ°€ μžˆμ–΄λ„ CGLIBλ₯Ό μ‚¬μš©ν•˜κ³ , 클래슀 기반 ν”„λ‘μ‹œ μ‚¬μš©")
    void proxyTargetClass() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        
        proxyFactory.setProxyTargetClass(true); 
        // proxy λ₯Ό λ§Œλ“œλŠ”λ° target 클래슀λ₯Ό 기반으둜 λ§Œλ“€κΊΌμ•Ό! 라고 μ„€μ •
        
        proxyFactory.addAdvice(new TimeAdvice());
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());

        proxy.save();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
        assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
    }

}

 

정리

ν”„λ‘μ‹œ νŒ©ν† λ¦¬μ˜ 좔상화 덕뢄에 ꡬ체적인 CGLIB, JDK 동적 ν”„λ‘μ‹œ κΈ°μˆ μ— μ˜μ‘΄ν•˜μ§€ μ•Šκ³  νŽΈλ¦¬ν•˜κ²Œ 동적 ν”„λ‘μ‹œλ₯Ό 생성할 수 있게 됐닀. λ˜ν•œ ν”„λ‘μ‹œμ˜ λΆ€κ°€κΈ°λŠ₯ λ‘œμ§λ„ νŠΉμ • κΈ°μˆ μ— μ’…μ†λ˜μ§€ μ•Šκ³ , Advice ν•˜λ‚˜λ‘œ νŽΈλ¦¬ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμ—ˆλ‹€. 이것은 ν”„λ‘μ‹œ νŒ©ν† λ¦¬ λ‚΄λΆ€μ—μ„œ JDK 동적 ν”„λ‘μ‹œμΈ 경우 InvocationHandler κ°€  Advice λ₯Ό ν˜ΈμΆœν•˜κ³ , CGLIB 인 경우 MethodInterceptor κ°€ Advice λ₯Ό ν˜ΈμΆœν•˜λ„λ‘ κ°œλ°œλ˜μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€ .

 

 

μ½”λ“œ 및 자료 좜처

 

μŠ€ν”„λ§ 핡심 원리 - κ³ κΈ‰νŽΈ - μΈν”„λŸ° | κ°•μ˜

μŠ€ν”„λ§μ˜ 핡심 원리와 κ³ κΈ‰ κΈ°μˆ λ“€μ„ 깊이있게 ν•™μŠ΅ν•˜κ³ , μŠ€ν”„λ§μ„ μžμ‹ μžˆκ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€., - κ°•μ˜ μ†Œκ°œ | μΈν”„λŸ°...

www.inflearn.com