[Spring] 동적 ν”„λ‘μ‹œ 기술(1) - λ¦¬ν”Œλ ‰μ…˜

2022. 6. 2. 12:28ㆍBackend/🌿 Spring

λ‘œκΉ…λ“±μ˜ λΆ€κ°€ 처리 κΈ°λŠ₯을 μœ„ν•΄ ν”„λ‘μ‹œ 클래슀λ₯Ό 맀번 λ§Œλ“€κΈ°μ—”, 적용 λŒ€μƒ 클래슀 수 만큼 ν”„λ‘μ‹œ 클래슀λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•˜λŠ” λ¬Έμ œκ°€ μžˆλ‹€. 둜그 좔적을 μœ„ν•œ ν”„λ‘μ‹œ 클래슀의 μ†ŒμŠ€μ½”λ“œλŠ” 거의 같은 λͺ¨μ–‘을 ν•˜κ³  μžˆλ‹€. 같은 λͺ¨μ–‘μ˜ 클래슀λ₯Ό μ—¬λŸ¬κ°œ λ§Œλ“œλŠ” 것은 λΉ„νš¨μœ¨μ μΌ λΏλ”λŸ¬ SRP 원칙을 μœ„λ°°ν•˜κΈ° λ•Œλ¬Έμ—, 동적 ν”„λ‘μ‹œ κΈ°μˆ μ„ ν™œμš©ν•˜μ—¬ 이 문제λ₯Ό μ μ§„μ μœΌλ‘œ ν•΄κ²°ν•΄λ³΄μž. 

 

λ¦¬ν”Œλ ‰μ…˜ μ΄λž€?

객체λ₯Ό 톡해 클래슀의 메타정보λ₯Ό λΆ„μ„ν•˜λŠ” ν”„λ‘œκ·Έλž¨ 기법을 μ˜λ―Έν•œλ‹€. 투영, λ°˜μ‚¬λΌλŠ” 사전적 의미λ₯Ό κ°€μ§€λŠ”λ°, 클래슀의 메타정보λ₯Ό νˆ¬μ˜ν•œλ‹€κ³  μ΄ν•΄ν•˜λ©΄ λœλ‹€.

그럼 λ­ν•˜λŸ¬ ꡳ이 객체λ₯Ό 톡해 클래슀의 정보λ₯Ό μ•Œμ•„λ‚΄μ•Όν• κΉŒ?

ꡬ체적인 클래슀의 νƒ€μž…μ„ μ•Œμ§€ λͺ»ν•΄λ„, ν•΄λ‹Ή ν΄λž˜μŠ€κ°€ 가지고 μžˆλŠ” λ©”μ†Œλ“œ, νƒ€μž…, λ³€μˆ˜ 등에 μ ‘κ·Όν•΄μ•Ό ν•  κ²½μš°κ°€ 있기 λ•Œλ¬Έμ΄λ‹€.

예λ₯Ό λ“€μ–΄ μ½”λ“œλ₯Ό μž‘μ„±ν•  μ‹œμ μ—λŠ” μ–΄λ–€ νƒ€μž…μ˜ 클래슀λ₯Ό μ‚¬μš©ν• μ§€ λͺ°λΌλ„ λ˜μ§€λ§Œ, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 싀행쀑인 λŸ°νƒ€μž„ μ‹œμ μ—λŠ” 싀행쀑인 클래슀λ₯Ό 가져와야 ν•  κ²½μš°κ°€ μžˆλ‹€. 이럴 λ•Œ λ¦¬ν”Œλ ‰μ…˜ 기법을 μ‚¬μš©ν•˜λ©΄ λ™μ μœΌλ‘œ 객체λ₯Ό 가져와 μ‹€ν–‰ν•  수 μžˆλ‹€.  μ•„λž˜ μ˜ˆμ‹œλ₯Ό λ³΄λ©΄μ„œ 더 μžμ„Ένžˆ μ•Œμ•„λ³΄μž. 

 

같은 둜그 κΈ°λŠ₯, μ€‘λ³΅λ˜λŠ” μ½”λ“œ

μ—¬κΈ°μ„œ κ³΅ν†΅λ˜λŠ” κΈ°λŠ₯은 log.info("start") 와 result의 둜그λ₯Ό μ°λŠ” 뢀뢄이고, κ°œλ³„ κΈ°λŠ₯은 각각의 κ°œλ³„ λ©”μ„œλ“œλ₯Ό 호좜( target.callA(), target.callB() )ν•˜λŠ” 뢀뢄이닀.

@Slf4j
public class ReflectionTest {

    @Test
    @DisplayName("둜그 μ°λŠ” 곡톡 둜직 사이에 κ°œλ³„ 둜직 μ‘΄μž¬ν•  λ•Œ")
    void reflection0() {
        Hello target = new Hello(); 
        
        //곡톡 둜직1 μ‹œμž‘
        log.info("start");
        String result1 = target.callA(); // λ‘œκΉ…μ„ μœ„ν•΄ ν˜ΈμΆœν•˜λŠ” λ©”μ„œλ“œκ°€ 닀름
        log.info("result={}", result1);
        //곡톡 둜직1 μ’…λ£Œ

        //곡톡 둜직2 μ‹œμž‘
        log.info("start");
        String result2 = target.callB(); // λ‘œκΉ…μ„ μœ„ν•΄ ν˜ΈμΆœν•˜λŠ” λ©”μ„œλ“œκ°€ 닀름
        log.info("result={}", result2);
        //곡톡 둜직2 μ’…λ£Œ
    }

    @Slf4j
    static class Hello {
        public String callA() {
            log.info("callA");
            return "A";
        }
        public String callB() {
            log.info("callB");
            return "B";
        }
    }
}

μ§€κΈˆμ€ callA(), callB() λ©”μ„œλ“œκ°€ μ „λΆ€μ΄μ§€λ§Œ, 둜그의 λŒ€μƒμ΄ λ˜λŠ” λ©”μ„œλ“œκ°€ 수백개둜 λŠ˜μ–΄λ‚œλ‹€λ©΄?

맀번 log.info("start") λ₯Ό 찍을 μˆ˜λ„ μ—†λŠ” 노릇이닀. 좔상화λ₯Ό 톡해 곡톡 λ‘œμ§μ„ 뽑아내고 μ‹Άμ§€λ§Œ, 쀑간에 ν˜ΈμΆœν•˜λŠ” κ°œλ³„ λ©”μ„œλ“œκ°€ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— 쉽지 μ•Šλ‹€. κ·Έλ ‡λ‹€λ©΄ ν˜ΈμΆœν•˜λŠ” λ©”μ„œλ“œμΈ target.callA(), target.callB() λΆ€λΆ„λ§Œ λ™μ μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλ‹€λ©΄ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

 

 

package hello.proxy.jdkdynamic;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@Slf4j
public class ReflectionTest {

    @Test
    @DisplayName("곡톡 둜직 μ²˜λ¦¬ν•˜λŠ” dynamicCall() 둜 둜그 처리")
    void reflection2() throws Exception {

        //클래슀 정보
        Class classHello = Class.forName("hello.proxy.jdkdynamic.ReflectionTest$Hello");

        Hello target = new Hello();
        Method methodCallA = classHello.getMethod("callA");
        dynamicCall(methodCallA, target); // λ©”μ„œλ“œ 정보와 target 만 λ„˜κΈ°λ©΄ λœλ‹€.

        Method methodCallB = classHello.getMethod("callB");
        dynamicCall(methodCallB, target);
    }

    // 곡톡 둜직 1, 2 λ₯Ό ν•œλ²ˆμ— μ²˜λ¦¬ν•  수 μžˆλŠ” ν†΅ν•©λœ 곡톡 처리 둜직.
    // Method method 인자 : ν˜ΈμΆœν•  λ©”μ„œλ“œμ˜ 정보가 λ„˜μ–΄μ˜¨λ‹€. κΈ°μ‘΄μ—λŠ” λ©”μ„œλ“œ 이름을 직접 ν˜ΈμΆœν–ˆλ‹€.
    private void dynamicCall(Method method, Object target) throws Exception {
        log.info("start");
        Object result = method.invoke(target);
        log.info("result={}", result);
    }

    @Slf4j
    static class Hello {
        public String callA() {
            log.info("callA");
            return "A";
        }
        public String callB() {
            log.info("callB");
            return "B";
        }
    }
}

 

μ½”λ“œλ₯Ό 라인 바이 라인 μ•Œμ•„λ³΄μž. 

  • method.invoke(target) : target μΈμŠ€ν„΄μŠ€μ— μžˆλŠ” method λ₯Ό ν˜ΈμΆœν•œλ‹€.  -> invoke() 의 인자둜 μžˆλŠ” μΈμŠ€ν„΄μŠ€μ˜ method(λ©”μ„œλ“œ λͺ…) λ₯Ό ν˜ΈμΆœν•œλ‹€.
  • Class.forName("클래슀λͺ…") : 클래슀의 메타정보λ₯Ό νšλ“ν•œλ‹€. 참고둜 λ‚΄λΆ€ ν΄λž˜μŠ€λŠ” ꡬ뢄을 μœ„ν•΄ $ λ₯Ό μ‚¬μš©ν•œλ‹€.
  • dynamicCall(Method method, Object target) : 곡톡 둜직 1, 2 λ₯Ό ν•œλ²ˆμ— μ²˜λ¦¬ν•  수 μžˆλŠ” ν†΅ν•©λœ 곡톡 처리 둜직. 
    • 첫 번째 νŒŒλΌλ―Έν„°μΈ Method method λŠ” ν˜ΈμΆœν•  λ©”μ„œλ“œ 정보에 ν•΄λ‹Ήλ˜λ©°, μœ„ μ˜ˆμ‹œμ—μ„œλŠ” Hello 클래슀의 callA(), callB() λ©”μ„œλ“œμ— ν•΄λ‹Ήλœλ‹€. 
    • κΈ°μ‘΄μ—λŠ” target.callB() ν˜•νƒœλ‘œ λ©”μ„œλ“œ 이름을 직접 ν˜ΈμΆœν–ˆμ§€λ§Œ, μ΄μ œλŠ” Method λΌλŠ” 메타정보λ₯Ό 톡해 ν˜ΈμΆœν•  λ©”μ„œλ“œ 정보λ₯Ό λ™μ μœΌλ‘œ νŒŒλΌλ―Έν„°ν™” ν•œλ‹€.
    • Object target : μ‹€μ œ μ‹€ν–‰ν•  μΈμŠ€ν„΄μŠ€ 정보가 λ„˜μ–΄μ˜¨λ‹€. μ‹€μ œ μ‹€ν–‰ν•  μΈμŠ€ν„΄μŠ€λΌ 함은, methodλ₯Ό μ •μ˜ν•œ μ‹€μ œ μΈμŠ€ν„΄μŠ€λ₯Ό μ˜λ―Έν•œλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— invoke() λ©”μ„œλ“œμ˜ 인자둜 μ‹€μ œ μΈμŠ€ν„΄μŠ€μΈ target 을 λ°›λŠ” 것이닀. λ¬Όλ‘  method.invoke(target) 을 μ‚¬μš©ν•  λ•Œ ν˜ΈμΆœν•  ν΄λž˜μŠ€μ™€ λ©”μ„œλ“œ 정보가 λ‹€λ₯΄λ©΄ μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.

 

정리

맀번 κ°œλ³„λ‘œ ν˜ΈμΆœν–ˆλ˜ target.callA(), target.callB() μ½”λ“œλ₯Ό λ¦¬ν”Œλ ‰μ…˜μ„ μ‚¬μš©ν•˜μ—¬ Method λΌλŠ” λ©”νƒ€μ •λ³΄λ‘œ μΆ”μƒν™”ν–ˆλ‹€. 

ν•˜μ§€λ§Œ λ¦¬ν”Œλ ‰μ…˜μ€ ν˜„μ—…μ—μ„œλŠ” 가급적 μ‚¬μš©μ„ μ§€μ–‘ν•΄μ•Όν•œλ‹€. μ™œλƒν•˜λ©΄ λ¦¬ν”Œλ ‰μ…˜ κΈ°μˆ μ€ λŸ°νƒ€μž„μ— λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ— 컴파일 μ‹œμ μ— 였λ₯˜λ₯Ό μž‘μ„ 수 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€. getMethod()의 인자둜 λ“€μ–΄κ°ˆ λ©”μ„œλ“œλͺ…에 μ˜€νƒ€κ°€ μžˆλ”λΌλ„ 컴파일 μ‹œμ μ—λŠ” 문제λ₯Ό 인지할 수 μ—†λ‹€. 즉 λŸ°νƒ€μž„μ΄ λ˜μ„œμ•Ό, μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ‹€ν–‰λ˜κ³  λ‚˜μ„œμ•Ό μ—λŸ¬κ°€ λ°œμƒν•  수 μžˆλŠ” 문제λ₯Ό 가지고 μžˆλ‹€. 

 

 

μ½”λ“œ 및 자료 좜처

 

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

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

www.inflearn.com