상세 컨텐츠

본문 제목

리플렉션(Reflection) -2

Development/Java

by thisisnew 2019. 10. 28. 18:32

본문

반응형

 

https://thisisnew-storage.tistory.com/10

 

리플렉션(Reflection) -1

관상(觀相)이라는 단어를 아시나요? 관상이란 사람의 생김새를 보고, 그 사람의 운명이나 성격 같은 정보를 알아내는 점법인데요. 자바에도 객체를 통해 그 객체의 원래 클래스와 그에 따르는 여러 정보들을 알아..

thisisnew-storage.tistory.com

저번 리플렉션 1편에서 클래스의 정보들을 추출하는 방법에 대해 알아보았는데요.

 

예제에서 생성자와 메서드, 그리고 변수들의 정보를 추출했었죠?

 

이번에는 그것들을 각각 어떻게 활용하는지 알아보도록 하겠습니다.


위의 사진과 같이, 리플렉션을 이용하면 동적으로 실행할 수 있다고 저번 1편에서 언급했었는데요.

 

동적으로 실행한다는 것은 코드가 특정 인스턴스를 찾아서, 그것을 실행하는 것을 의미하죠?

 

이게 과연 코드로는 어떻게 구현되는 것인지 한번 보도록 하죠.

 

이번 편에서는 생성자와 메서드를 동적으로 이용하는 것부터 알아보도록 하겠습니다.


생성자를 이용한 객체 생성

생성자의 필요성은 객체를 생성하기 위함이라고 1편에서 말씀드렸었는데요.

 

다만 1편에서는 생성자의 정보를 복수(複數)로 가져왔었죠?

 

이번에는 필요한 생성자 하나에 접근하여 객체를 생성해 보도록 하겠습니다.

 

생성자의 정보를 추출하는 방법은 여러 가지가 있는데요. 3가지만 소개해 드리겠습니다.

  • getConstructor(parameterTypes) : public으로 선언된 생성자에 접근 (단일 매게 변수 필요).
  • getConstructor(new Class[]{parameterTypes, parameterTypes,...}) :public으로 선언된 생성자에 접근 (복합 매게 변수 필요)
  • getDeclaredConstructor(parameterTypes) : 모든 생성자에 접근 (단일 매게 변수 필요). 단, setAccessible(true) 필요.

저는 이 중에 3번째인 getDeclaredConstructor(parameterTypes)를 이용한 코드를 보여드릴까 합니다.

//ReflectionTest.java
  
private ReflectionTest(Tool use) {
   setUse(use);
   System.out.println("Tool : " + getUse());
}

그럼, ReflectionTest 클래스에 선언된 use를 매게 변수로 하는 ReflectionTest 생성자를 가져와보도록 할게요.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Constructor;

public class GetReflectionTest {
    public static void main(String[] args) {

      Class refClass = null;

      try {
      	refClass = Class.forName("com.thisisnew.reflection.ReflectionTest");
      } catch (ClassNotFoundException e) {
      	e.printStackTrace();
      }
      
      try {
      	Constructor refDecConst = refClass.getDeclaredConstructor(Tool.class);
        
        refDecConst.setAccessible(true);
        
        refDecConst.newInstance(Tool.ECLIPSE);
        
      } catch (Exception e) {
      	e.printStackTrace();
      }
  }
}

매게 변수의 타입이 Tool이라는 enum 타입이므로 getDeclaredConstructor() 메서드 안에 Tool.class를 넣어서 접근했습니다.

 

그리고 여기서 중요한 것.

 

getDeclaredConstructor()는 private에 접근할 수 있지만 그런 경우에는 setAccessible(true)라는 메서드를 추가로 작성해주는 것이 필요합니다.

 

그 후 newInstance(initargs)라는 메서드를 이용하여 객체를 생성해주면 됩니다.

 

그럼 결과를 볼까요?

 

정상적으로 객체를 생성한 것을 볼 수 있습니다.

 

다음은 메서드를 동적으로 실행하는 방법에 대해 알아보도록 하죠.


메서드를 동적으로 이용하는 방법

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

public class GetReflectionTest {
  public static void main(String[] args) {

    try {
    
    	Class refClass = Class.forName("com.thisisnew.reflection.ReflectionTest");
        
        Method[] refMethod = refClass.getDeclaredMethods();

        for(int i=0; i<refMethod.length; i++) {
          System.out.println(refMethod[i].getName()); // toString() -> getName()
        }
        
    } catch (ClassNotFoundException e) {
    	e.printStackTrace();
    }
    
  }
}

 

1편에서 ReflectionTest 클래스의 메서드들을 추출하는 코드를 가져오도록 합니다.

 

다만 1편과 달리, getName() 메서드를 사용하여 메서드들의 이름만 추출해 낼 겁니다.

 

동작시켜보면 이렇게 나옵니다.

 

자, 이 중에서 우리가 원하는 메서드를 찾아서 실행해 보도록 할 건데요.

 

여기서는 setName 메서드를 실행시켜 보도록 하겠습니다.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

public class GetReflectionTest {
  public static void main(String[] args) {
	
    String[] nameArr = {"홍길동","임꺽정","아무개","라이언"}; // nameArr 추가

    try {
    
    	Class refClass = Class.forName("com.thisisnew.reflection.ReflectionTest");
        
        Method[] refMethod = refClass.getDeclaredMethods();

        for(int i=0; i<refMethod.length; i++) {
          System.out.println(refMethod[i].toString());
        }
        
    } catch (ClassNotFoundException e) {
    	e.printStackTrace();
    }
    
  }
}

setName() 메서드는 매게 변수(문자열)가 필요한데, 우리는 여기에 반복문으로 차례대로 집어넣으면서 setName() 메서드가 어떻게 동작하는지 볼 것입니다.

 

그래서 배열로 된 문자열 타입의 변수 nameArr을 선언해 주었습니다.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

public class GetReflectionTest {
  public static void main(String[] args) {
	
    String[] nameArr = {"홍길동","임꺽정","아무개","라이언"};
  
    try {
    	Class refClass = Class.forName("com.thisisnew.reflection.ReflectionTest");
        Method[] refMethod = refClass.getDeclaredMethods();
        
        try {
         for(int i=0; i<refMethod.length; i++) {
           if(refMethod[i].getName().equals("setName")) {
            System.out.println("-----------setName 찾음 -----------");
            System.out.println(refMethod[i].getName());
           }
         }
        } catch (Exception e) {
           e.printStackTrace();
        }
        
    } catch (ClassNotFoundException e) {
    	e.printStackTrace();
    }
  }
}

그다음, 반복문과 조건문을 이용하여 먼저 setName() 메서드가 찾아지는지 봐야겠죠?

 

이게 찾아지면 동적으로 실행할 수 있다는 의미니까요.

 

정상적으로 찾아지는군요.

 

그럼 이제 여기서 알아야 할 것이 있습니다.

 

setName() 메서드가 private인지 public인지.

 

만약 private으로 선언되어 있다면 setAccessible(true)를 먼저 추가해주어야 합니다.

 

//ReflectionTest.java

public void setName(String name) {
  this.name = name;
}

 

하지만 setName() 메서드는 public으로 선언되어 있군요. 그럼 바로 동작시킬 메서드가 필요한데요.

 

바로 invoke(obj, args)라는 메서드입니다.

 

리플렉션에서 메서드를 동적으로 실행하기 위해서는메서드를 사용해야 되는데요. 매게 변수를 2개 필요로 합니다.

 

obj는 메서드(여기서는 setName())실행하기 위한 객체이고, args는 메서드(setName())실행하기 위해 필요한 매게 변수입니다.

 

그럼 invoke() 메서드는 어떻게 사용하는지 아래의 코드를 보겠습니다.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

public class GetReflectionTest {
  public static void main(String[] args) {
	
    String[] nameArr = {"홍길동","임꺽정","아무개","라이언"};
  
    try {
    	ReflectionTest reflectionTest = new ReflectionTest(); //invoke()를 실행하는데 필요한 객체 생성
        Class refClass = Class.forName("com.thisisnew.reflection.ReflectionTest");
        Method[] refMethod = refClass.getDeclaredMethods();
        
        try {
         for(int i=0; i<refMethod.length; i++) {
           if(refMethod[i].getName().equals("setName")) {
             for(int j=0; j<nameArr.length; j++) {
               System.out.println("-----------setName 실행 -----------");
               refMethod[i].invoke(reflectionTest, nameArr[j]); //객체와 매게변수를 넣고 실행
               
               System.out.println("-----------getName 실행 -----------");
               System.out.println(reflectionTest.getName());
             }
           }
         }
        } catch (Exception e) {
           e.printStackTrace();
        }
        
    } catch (ClassNotFoundException e) {
    	e.printStackTrace();
    }
  }
}

먼저, invoke() 메서드를 사용하기 위해 reflectionTest라는 이름의 객체를 하나 생성하였습니다.

 

그리고 setName()을 찾아서 침투한 조건문 안쪽에서 invoke() 메서드에 reflectionTest와 nameArr를 넣어서 동작시켰습니다.

 

이때 반복문을 한번 더 사용하여 nameArr을 차례대로 집어넣으면서 setName() 메서드를 연속적으로 동작시켰습니다.

 

결과를 보도록 하죠.

 

어떤가요? setName() 메서드가 연속적으로 동작하면서, 세팅되는 문자열의 값이 달라지는 것을 볼 수 있죠?

 

getName() 메서드 자리에 필요한 코드를 작성하면 동적으로 실행되는 setName() 메서드를 편의에 따라 이용할 수 있습니다.

 

이번에는 생성자와 메서드를 동적으로 활용하는 방법에 대해 알아봤으니, 3편에서는 변수를 활용하는 방법에 대해 알아보도록 하겠습니다.

 

읽어주셔서 감사합니다.

반응형

관련글 더보기

댓글 영역