[Spring] 동적 ν”„λ‘μ‹œ 기술 (2) - JDK DynamicProxy

2022. 6. 2. 12:28γ†πŸŒΏ Spring

동적 ν”„λ‘μ‹œ κΈ°μˆ μ€ μ–Έμ œ μ‚¬μš©λ κΉŒ?

ν”„λ‘μ‹œ κΈ°μˆ μ„ μ μš©ν•˜κΈ° μœ„ν•¨μΈλ°, μ•žμ— λ™μ μ΄λΌλŠ” 단어가 μ™œ λΆ™μ—ˆλŠ”μ§€ μƒκ°ν•΄λ³΄μž. 

λ§Œμ•½ 100개의 ν΄λž˜μŠ€κ°€ ν”„λ‘μ‹œ 적용 λŒ€μƒμ΄λΌκ³  ν•˜μž. 그럼 100개의 ν΄λž˜μŠ€μ— μ μš©ν•  100개의 ν”„λ‘μ‹œλ₯Ό κ°œλ³„μ μœΌλ‘œ λ§Œλ“€μ–΄μ•Όν• κΉŒ? λ‹¨μˆœ 둜그λ₯Ό μ μš©ν•˜λŠ” κΈ°λŠ₯의 ν”„λ‘μ‹œλΌλ©΄, λ‘œμ§μ€ 같은데 적용 λŒ€μƒμ—λ§Œ 차이가 μžˆλŠ” 것이닀.

 

이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλŠ” 것이 동적 ν”„λ‘μ‹œ κΈ°μˆ μ΄λ‹€. 동적 ν”„λ‘μ‹œ κΈ°μˆ μ„ ν™œμš©ν•˜λ©΄ κ°œλ°œμžκ°€ 맀번 ν”„λ‘μ‹œ 클래슀λ₯Ό λ§Œλ“€μ§€ μ•Šμ•„λ„ λœλ‹€. μ΄λ¦„λŒ€λ‘œ ν”„λ‘μ‹œ 객체λ₯Ό λ™μ μœΌλ‘œ λŸ°νƒ€μž„μ— (개발자 λŒ€μ‹ ) λ§Œλ“€μ–΄μ€€λ‹€.  그럼 μ½”λ“œλ₯Ό λ³΄λ©΄μ„œ μ–΄λ–€μ‹μœΌλ‘œ 동적 ν”„λ‘μ‹œκ°€ μƒμ„±λ˜κ³  μ μš©λ˜λŠ”μ§€ ν™•μΈν•΄λ³΄μž. 참고둜 JDK 동적 ν”„λ‘μ‹œλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό 기반으둜 동적 ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•˜κΈ° λ•Œλ¬Έμ—, μΈν„°νŽ˜μ΄μŠ€κ°€ ν•„μˆ˜λ‹€. 

 

 

AImpl κ³Ό BImpl 은 각각 AInterface 와 BInterfaceλ₯Ό κ΅¬ν˜„ν•œλ‹€. 두 μΈν„°νŽ˜μ΄μŠ€ λͺ¨λ‘ call() 좔상 λ©”μ„œλ“œλ₯Ό κ°–λŠ”λ‹€.

// AInterface
public interface AInterface {
    String call();
}

@Slf4j
public class AImpl implements AInterface {
    @Override
    public String call() {
        log.info("A 호좜");
        return "a";
    }
}



// BInterface
public interface BInterface {
    String call();
}

@Slf4j
public class BImpl implements BInterface {
    @Override
    public String call() {
        log.info("B 호좜");
        return "b";
    }
}

 

InvocationHandler

JDK 동적 ν”„λ‘μ‹œκ°€ μ œκ³΅ν•˜λŠ” InvocationHander λ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„  InvocationHandler μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λ©΄ λœλ‹€. 

μ•„λž˜ InvocationHandler μΈν„°νŽ˜μ΄μŠ€λŠ” JDKμ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ νŒ¨ν‚€μ§€λ‹€.

package java.lang.reflect; // μžλ°”μ—μ„œ 기본으둜 μ œκ³΅ν•˜λŠ” reflect νŒ¨ν‚€μ§€

public interface InvocationHandler {
    Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}

 

TimeInvocationHandler

InvocationHander μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œλ‹€. μƒμ„±μžλ‘œ ν”„λ‘μ‹œλ₯Ό μ μš©ν•  λŒ€μƒμΈ target 객체λ₯Ό μ£Όμž…λ°›λŠ”λ‹€. ν”„λ‘μ‹œλŠ” λŒ€λ¦¬μžλΌλŠ” 의미λ₯Ό 가지기에 항상 원본 target 이 ν•„μš”ν•˜λ‹€κ³  이해할 수 μžˆλ‹€. 

@Slf4j // μ‹€ν–‰ μ‹œκ°„μ„ μ²΄ν¬ν•˜κΈ° μœ„ν•œ 둜그 -> 이걸 λŒ€μƒ ν΄λž˜μŠ€μ— μ‚½μž…ν•˜κ² λ‹€.
public class TimeInvocationHandler implements InvocationHandler { // JDK 동적 ν”„λ‘μ‹œμ— μ μš©ν•  곡톡 λ‘œμ§μ„ λ‹΄λŠ” 클래슀

    private final Object target; // Proxy λŠ” 항상 ν”„λ‘μ‹œκ°€ ν˜ΈμΆœν•  λŒ€μƒμ΄ ν•„μš”

    public TimeInvocationHandler(Object target) {
        this.target = target;
    }

    @Override  // Proxy 객체가 call ν•˜λ©΄ μ—¬κΈ°(invoke)κ°€ μˆ˜ν–‰λ¨
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("TimeProxy μ‹€ν–‰");
        long startTime = System.currentTimeMillis();

        // λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” 뢀뢄이 동적이닀 => μ–΄λ– ν•œ ν΄λž˜μŠ€λ‚˜ 그에 μ†ν•˜λŠ” λ©”μ„œλ“œλ₯Ό λ‹€ μ‚¬μš©ν•  수 μžˆλ‹€λŠ” 뜻
        Object result = method.invoke(target, args); // AImpl or BImpl 의 call() μ‹€ν–‰. κ²°κ³Όλ₯Ό result 에 λ‹΄μŒ.

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

 

ν…ŒμŠ€νŠΈ μ½”λ“œλ‘œ TimeInvocationHandler λ₯Ό μ‹€μ œ 객체에 μ μš©ν•΄λ³΄μž. 

@Slf4j
public class JdkDynamicProxyTest {

    @Test
    void dynamicA() {
        AInterface target = new AImpl();
        TimeInvocationHandler handler = new TimeInvocationHandler(target); 
        // handler : 동적 ν”„λ‘μ‹œμ— μ μš©ν•  둜직이 돀닀.

        // Interface κ°€ μ—¬λŸ¬κ°œ μžˆμ„ 수 μžˆμœΌλ‹ˆ, λ°°μ—΄λ‘œ λ°›λŠ”λ‹€.
        AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
        // λ™μ μœΌλ‘œ ν”„λ‘μ‹œ 객체λ₯Ό 생성해낸닀.

        // client λŠ” JDK 동적 ν”„λ‘μ‹œμ˜ call 을 μ‹€ν–‰
        proxy.call(); // AInterface 에 call() λ©”μ„œλ“œ 쑴재
        // call() ν•˜λ©΄ proxy λŠ” handler 의 λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€.
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    }

    @Test
    void dynamicB() {
        BInterface target = new BImpl();
        TimeInvocationHandler handler = new TimeInvocationHandler(target);

        BInterface proxy = (BInterface) Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[]{BInterface.class}, handler);

        proxy.call();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    }
}
  • new TimeInvocationHandler(target) : 동적 ν”„λ‘μ‹œμ— μ μš©ν•  ν•Έλ“€λŸ¬ λ‘œμ§μ„ λ¦¬ν„΄ν•œλ‹€. 
  • Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler) 
    • 동적 ν”„λ‘μ‹œλŠ” java.lang.reflect.Proxy λ₯Ό 톡해 생성할 수 μžˆλ‹€.
    • 인자둜 1) 클래슀 λ‘œλ” 정보, 2) μΈν„°νŽ˜μ΄μŠ€, 3) ν•Έλ“€λŸ¬ λ‘œμ§μ„ λ„£μ–΄μ£Όλ©΄ λœλ‹€. ν•΄λ‹Ή μΈν„°νŽ˜μ΄μŠ€λ₯Ό 기반으둜 동적 ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•œλ‹€. 

 

dynamicA() ν…ŒμŠ€νŠΈ κ²°κ³Ό

 

μ‹€ν–‰ μˆœμ„œ

1. ν΄λΌμ΄μ–ΈνŠΈλŠ” JDK 동적 ν”„λ‘μ‹œμ˜ call() 을 μ‹€ν–‰ν•œλ‹€. 이 call() 은 AInterface / Binterface 에 μ„ μ–Έλœ 좔상 λ©”μ„œλ“œλ₯Ό μ˜λ―Έν•œλ‹€. 

ν•˜μ§€λ§Œ ν”„λ‘μ‹œμ˜ call() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν–ˆμœΌλ―€λ‘œ, AImpl, BImpl에 κ΅¬ν˜„λœ call() λ©”μ„œλ“œλ₯Ό λ°”λ‘œ ν˜ΈμΆœν•˜μ§€ μ•Šκ³ , proxy 객체의 call() 을 ν˜ΈμΆœν•œλ‹€.

 

2. InvocationHandler 의 invoke() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ”λ°, TimeInvocationHandler κ°€ κ΅¬ν˜„μ²΄λ‘œ μžˆμœΌλ―€λ‘œ TimeInvocationHandler.invoke() κ°€ ν˜ΈμΆœλœλ‹€. 

 

3. TimeInvocationHandler 에 μ •μ˜λœ λ‚΄λΆ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜κ³ , method.invoke(target, args) λ₯Ό ν˜ΈμΆœν•΄μ„œ target에 ν•΄λ‹Ήλ˜λŠ” μ‹€μ œ 객체(AImpl / BImpl) 을 ν˜ΈμΆœν•œλ‹€. 

 

4. AImpl / BImpl 의 call() λ©”μ„œλ“œκ°€ μ‹€ν–‰λœλ‹€. 

 

5. call() λ©”μ„œλ“œκ°€ μ’…λ£Œλ˜λ©΄ λ‹€μ‹œ TimeInvocationHandler 둜 응닡이 λŒμ•„μ˜€κ³ , μ‹œκ°„ 둜그λ₯Ό 좜λ ₯ν•˜μ—¬ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•œλ‹€.

 

쀑간 정리

JDK 동적 ν”„λ‘μ‹œ 기술 덕뢄에, AImpl, BImpl 각각의 ν”„λ‘μ‹œλ₯Ό λ§Œλ“€μ§€ μ•Šμ•„λ„, ν”„λ‘μ‹œ κΈ°μˆ μ„ μ μš©ν•  수 μžˆμ—ˆλ‹€. μ„œλ‘μ—μ„œ μ–ΈκΈ‰ν•œ 100개의 ν΄λž˜μŠ€κ°€ μ‘΄μž¬ν•΄λ„, 동적 ν”„λ‘μ‹œ κΈ°μˆ μ„ μ μš©ν•˜λ©΄ SRP λ₯Ό μ§€ν‚€λ©΄μ„œ ν”„λ‘μ‹œλ₯Ό μ μš©ν•  수 μžˆλ‹€. 

 

JDK 동적 ν”„λ‘μ‹œ - ν•„ν„° 

둜그λ₯Ό λ‚¨κΈ°λŠ” ν”„λ‘μ‹œλ₯Ό μ μš©ν•  λ•Œ, νŠΉμ • λ©”μ„œλ“œμ—λŠ” 둜그 κΈ°λŠ₯을 μ œμ™Έν•˜κ³  싢을 수 μžˆλ‹€. 그런 경우, PatternMatchUtils.simpleMatch() 같은 맀칭 λ‘œμ§μ„ μ μš©ν•˜μ—¬ 곡톡 둜직(λ‘œκΉ…)을 μ œμ™Έν•  수 μžˆλ‹€. FilterInvocationHandler λŠ” if λ¬Έμ—μ„œ λΆ„κΈ°μ²˜λ¦¬ν•˜μ—¬ 둜그λ₯Ό μ μš©ν•  λŒ€μƒμ„ κ±°λ₯Έλ‹€. 

@Slf4j
public class FilterInvocationHandler implements InvocationHandler {

    private final Object target;
    private final String[] patterns;

    public FilterInvocationHandler(Object target, String[] patterns) {
        this.target = target;
        this.patterns = patterns;
    }

    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String methodName = method.getName();
        if (!PatternMatchUtils.simpleMatch(patterns, methodName)) {
            return method.invoke(target, args);
        }
        log.info("TimeProxy μ‹€ν–‰");

        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeProxy μ’…λ£Œ resultTime={}", resultTime);
        return result;
    }
}
// AImpl 도 μ•„λž˜μ™€ 같은 ꡬ쑰λ₯Ό 가지며, 둜그 λ‚΄μš©λ§Œ λ‹€λ₯΄λ‹€. 
@Slf4j
public class BImpl implements BInterface {
    @Override
    public String call() {
        log.info("B 호좜");
        return "b";
    }

    @Override
    public String filterCall() {
        log.info("B ν•„ν„°");
        return "filter";
    }
}
@Slf4j
public class JDKDynamicProxyFilterTest {
    
    public static final String[] PATTERNS = {"call*"};

    @Test
    void dynamicA() {
        AInterface target = new AImpl(); 
        TimeInvocationHandler handler = new TimeInvocationHandler(target);

        AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);


        proxy.call();
        proxy.filterCall();
    }

    @Test
    void dynamicB() {
        BInterface target = new BImpl();
        FilterInvocationHandler handler = new FilterInvocationHandler(target, PATTERNS); // 동적 ν”„λ‘μ‹œμ— μ μš©ν•  둜직이 돀닀.

        BInterface proxy = (BInterface) Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[]{BInterface.class}, handler);

        proxy.call();
        proxy.filterCall();
    }
}

 

dynamicA() λ©”μ„œλ“œμ—μ„œλŠ” κΈ°μ‘΄ TimeInvocationHandler() λ₯Ό ν˜ΈμΆœν–ˆλ‹€. proxy.call(), proxy.filterCall() λ©”μ„œλ“œ λͺ¨λ‘ time logging 이 ν•¨κ»˜ 좜λ ₯λ˜λŠ” 것을 λ³Ό 수 μžˆλ‹€.

 

dynamicB() λ©”μ„œλ“œμ—μ„œλŠ”, ν•„ν„°μ²˜λ¦¬λ₯Ό ν•  FilterInvocationHandler() λ₯Ό ν˜ΈμΆœν–ˆκ³ , PATTERNS κΉŒμ§€ μƒμ„±μž μ£Όμž…μ„ λ°›λŠ”λ‹€.  κ·Έ κ²°κ³Ό proxy.call() μ—μ„œλŠ” νƒ€μž„ λ‘œκΉ…μ΄ μ°νžˆμ§€λ§Œ, proxy.filterCall() μ—μ„œλŠ” νƒ€μž„ λ‘œκΉ…μ΄ μ°νžˆμ§€ μ•ŠλŠ” 것을 확인할 수 μžˆλ‹€.

 

정리

JDK 동적 ν”„λ‘μ‹œλŠ” μΈν„°νŽ˜μ΄μŠ€κ°€ ν•„μˆ˜λ‹€. 즉 κ΅¬ν˜„μ²΄λ‘œλ§Œ 이루어진 ν΄λž˜μŠ€μ—λŠ” 동적 ν”„λ‘μ‹œλ₯Ό μ μš©ν•  수 μ—†λ‹€λŠ” 뜻인데, 이런 κ²½μš°μ—λŠ” CGLIB λΌλŠ” λ°”μ΄νŠΈμ½”λ“œ μ‘°μž‘ 라이브러리λ₯Ό μ‚¬μš©ν•΄μ•Όν•œλ‹€.  λ‹€μŒ ν¬μŠ€νŒ…μ—μ„  CGLIB λ₯Ό ν™œμš©ν•œ 동적 ν”„λ‘μ‹œλ₯Ό μ μš©ν•΄λ³΄μž .

 

μ½”λ“œ 및 자료 좜처

 

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

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

www.inflearn.com