상세 컨텐츠

본문 제목

리플렉션(Reflection) -1

Development/Java

by thisisnew 2019. 10. 25. 13:05

본문

반응형

 

관상(相)이라는 단어를 아시나요?

 

관상이란 사람의 생김새를 보고, 그 사람의 운명이나 성격 같은 정보를 알아내는 점법인데요.

 

자바에도 객체를 통해 그 객체의 원래 클래스와 그에 따르는 여러 정보들을 알아낼 수 있는 방법이 있습니다.

 

이것을 리플렉션(Reflection)이라고 하는데요. 

 

정보들을 알아내는 것에서 그치지 않고, 이 정보들을 수정하거나 실행할 수도 있습니다.

 

리플렉션이 필요한 상황은 과연 언제일까요?

 

다음 사진을 보도록 하겠습니다.

 

자바의 기본 API 중 하나인 Date 클래스를 이용하는 상황입니다.

 

이클립스(Eclipse)나 인텔리제이(Intellij) 같은 IDE(Integrated development environment)로 개발을 할 때, 사진에 나온 것처럼 자동완성 기능을 다들 사용하실 텐데요.

 

자동완성 기능 덕분에 우리는 Date 클래스가 제공하는 여러 메서드들을 손쉽게 찾아서 이용할 수 있죠.

 

그럼 과연 IDE는 어떻게 date 변수에 적합한 메서드를 우리에게 보여주는 걸까요? 

 

date 변수의 타입이 Date 클래스인 것을 IDE가 먼저 확인했기 때문입니다.

 

이것이 바로 리플렉션입니다. 

 

일반적인 객체의 생성은 우리가 클래스와 메서드를 직접 작성하면, 그것이 컴파일되어 메모리에 올라가는 것을 전제로 하는데요. 

 

정적(靜的) 언어인 자바는 실행 전에 실행될 모습이 어느 정도 결정되어 있어야 하기 때문입니다.

 

하지만 리플렉션을 이용하면 런타임 즉, 프로그램이 실행되고 있는 동안에도 클래스의 정보를 알아내는 것이 가능하게 됩니다.

 

그리고 이는 바로 동적(動的)으로 그것을 실행(동작 및 수정) 할 수 있게 된다는 것을 의미합니다.


클래스 파일의 정보를 얻기 위한 자바에서 제공하는 API가 있는데요.

 

바로 'java.lang.class'입니다.

 

Class 클래스라고 하는데요. 위에서 언급한 Date 클래스, 혹은 String 클래스와 같은 클래스 중에 하나입니다.

 

그럼 이것을 이용하여 클래스의 객체를 받아오는 3가지 방법을 보여드리도록 하죠.

//ReflectionTest.java

package com.thisisnew.reflection;

public class ReflectionTest {
	
  private String name;
  private String gender;
  private Tool use;

  public enum Tool{
    INTELLIJ, ECLIPSE
  }
  
  public ReflectionTest() { 
  
  }
  
  private ReflectionTest(Tool use) { //private
    setUse(use);
    System.out.println("Tool : " + getUse());
  }
  
  private void setNameAndUse(String name, Tool use) { //private
    setName(name);
    setUse(use);
  }
  
  public Tool getUse() {
    return use;
  }

  public void setUse(Tool use) {
    this.use = use;
  }

  public String getName() {
    return name;
  }

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

  public String getGender() {
    return gender;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }
}

먼저 ReflectionTest라는 이름의 클래스 파일을 만들도록 하겠습니다. (패키지명은 com.thisisnew.reflection).

 

그리고 변수들과 그에 따른 Getter/Setter 메서드도 작성하고요.

 

단, 여기에 private으로 ReflectionTest라는 생성자와 setNameAndUse()라는 메서드를 추가하였습니다.

 

자, 이제 ReflectionTest 객체를 가져와 보도록 할게요.

 

ReflectionTest 객체를 가져오는 방법은 3가지가 있는데요.

Class refClass1 = ReflectionTest.class;

먼저. class를 붙이는 이 방법은 해당 클래스를 바로 참조하는 것입니다.

ReflectionTest reflectionTest = new ReflectionTest();
Class refClass2 = reflectionTest.getClass();

두 번째로는 이렇게 객체 생성 후, getClass()라는 메서드를 이용하는 방법입니다.

Class refClass3 = Class.forName("com.thisisnew.reflection.ReflectionTest");

그리고 마지막으로 forName() 메서드를 이용하여 괄호 안에 기술된 클래스를 로드하는 방법입니다.

 

위의 두 방법이 좀 더 명시적이었다면, 세번째 방법은 동적인 코드를 작성하는데 더 나은 장점을 가지고 있습니다.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

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

    Class refClass1 = ReflectionTest.class; //첫번째 방법

    ReflectionTest reflectionTest = new ReflectionTest();
    Class refClass2 = reflectionTest.getClass(); //두번째 방법

    try {
    	Class refClass3 = Class.forName("com.thisisnew.reflection.ReflectionTest"); //세번째 방법
    } catch (ClassNotFoundException e) {
    	e.printStackTrace();
    }
    
  }
}

이렇게 예외 처리 같은 부분에서 말이죠.

 

이제 세 가지 방법을 전부 이용하여 간단한 예제 코드를 작성해 보도록 하겠습니다.


객체 생성을 위한 생성자 정보 가져오기

자바의 객체 생성에는 생성자가 필요하기 때문에, 생성자의 정보를 가져오는 것은 매우 중요합니다.

 

그럼 ReflectionTest 클래스에 기작성했던 생성자의 정보들을 어떻게 가져올 수 있는지 알아봐야겠죠?

 

생성자의 정보를 가져오는 메서드에는 여러가지가 있지만, 여기서는 2가지만 소개하도록 하겠습니다.

  • getConstructors() : 해당 클래스에 public으로 선언된 모든 생성자를 배열의 형태로 반환합니다.
  • getDeclaredConstructors() : 해당 클래스에 선언된 모든 생성자를 배열의 형태로 반환합니다. 여기에는 private으로 선언된 생성자도 포함됩니다.
//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();
      }

      System.out.println("------------------getConstructors() 메서드------------------");
      
      Constructor[] refConstructors = refClass.getConstructors();
      
      for(int i=0; i<refConstructors.length; i++) {
        System.out.println(refConstructors[i].toString());
      }
      
      
      
      System.out.println("------------------getDeclaredConstructors() 메서드------------------");

      Constructor[] refDecConst = refClass.getDeclaredConstructors();

      for(int i=0; i<refDecConst.length; i++) {
      	System.out.println(refDecConst[i].toString());
      }
      
  }
}

그럼, 결과를 보도록 하죠.

 

두 메서드의 차이가 보이시죠? getDeclaredConstructors()로 접근할 경우, private으로 선언된 생성자에도 접근하는 것을 확인할 수 있습니다.

 

이렇게 얻은 생성자의 정보를 이용하여 객체를 생성하는 것은 2편에서 다루겠습니다.

 

메서드 정보 가져오기

먼저 ReflectionTest 클래스에 기작성했던 메서드들을 가져올 수 있는지 볼까요?

 

Class 클래스에서 제공하는 메서드에는 getMethods()와 getDeclaredMethods()가 있는데요.

  • getMethods() : public으로 선언된 모든 메서드 목록과 해당 클래스에서 상속받은 메서드에 접근합니다.
  • getDeclaredMethods() : 해당 클래스에서 선언된 메서드 정보를 반환합니다. 당연히 여기에는 private으로 선언된 메서드도 포함됩니다.
//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Method;

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();
      }

      System.out.println("------------------getMethods() 메서드------------------");
      
      Method[] refMethod = refClass.getMethods();
      
      for(int i=0; i<refMethod.length; i++) {
        System.out.println(refMethod[i].toString());
      }
      
      
      
      System.out.println("------------------getDeclaredMethods() 메서드------------------");

      Method[] refDecMethod = refClass.getDeclaredMethods();

      for(int i=0; i<refDecMethod.length; i++) {
      	System.out.println(refDecMethod[i].toString());
      }
      
  }
}

두 메서드 모두 메서드명이 복수(複數)이므로 Method 타입의 배열로 반환합니다.

 

그럼 결과를 한번 볼까요?

ReflectionTest 클래스에 기작성했던 메서드들이 추출되는 것이 보이네요.

 

단, getDeclaredMethods()로 접근할 경우에는 setNameAndUse()라는 private으로 선언된 메서드에 접근하는 것을 볼 수 있습니다.(노란색 박스)

 

변수 정보 가져오기

이번에는 변수들도 가져올 수 있는지 확인해 보죠.

//GetReflectionTest.java

package com.thisisnew.reflection;

import java.lang.reflect.Field;

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();
      }

      System.out.println("------------------getFields() 메서드------------------");
      
      Field[] refFields = refClass.getFields();
      
      for(int i=0; i<refFields.length; i++) {
        System.out.println(refFields[i].toString());
      }
      
      
      
      System.out.println("------------------getDeclaredFields() 메서드------------------");

      Field[] refDecFields = refClass.getDeclaredFields();

      for(int i=0; i<refDecFields.length; i++) {
      	System.out.println(refDecFields[i].toString());
      }
      
  }
}

getFields() 또는 getDeclaredFields()라는 메서드를 이용하면 되는데요.

 

  • getFields() : public으로 선언된 변수 목록을 Field 타입의 배열로 반환합니다.
  • getDeclaredFields() : 해당 클래스에서 정의 된 모든 변수 목록을 배열의 형태로 반환합니다.

결과를 보도록 하죠.

ReflectionTest 클래스에 선언된 변수들은 전부 private이었기 때문에 getDeclaredFields() 메서드는 모든 변수의 정보를 추출하지만, getFields() 메서드는 아무것도 추출해내지 못하는 것을 볼 수 있습니다.


JDBC에서의 리플렉션 활용

위의 사진과 같이 JDBC를 이용한 데이터베이스 연결에서도 리플렉션의 활용을 볼 수 있는데요.

 

데이터베이스에 연결하기 위해서는 알맞은 드라이버를 로드해야 하기 때문입니다.

 

드라이버가 로드되면 그 후 getConnection()이라는 메서드를 이용하여 연결하는 식이죠.


Class 클래스에서 제공하는 많은 메서드들을 이용하면 다양한 곳에 활용할 수 있는데요.

 

아래의 문서를 참고하시기 바랍니다.

 

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

 

Class (Java Platform SE 8 )

Determines if the specified Class object represents a primitive type. There are nine predefined Class objects to represent the eight primitive types and void. These are created by the Java Virtual Machine, and have the same names as the primitive types tha

docs.oracle.com


그럼 클래스의 정보를 알아내는 것은 됐으니, 이들을 동적으로 실행하는 것에 대해 알아봐야겠죠?

 

그것은 2편에서 보도록 하겠습니다.

 

 

반응형

관련글 더보기

댓글 영역