티스토리 뷰

Language/JAVA

[JAVA]Reflection??

retto9522 2024. 9. 13. 13:50

Reflection

Reflection은 구체적인 클래스 정보를 알지 못하더라도, 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 도와주는 자바 API입니다.

컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법입니다. 이는 동적 바인딩과 관련이 있습니다.

 

Static Binding VS Dynamic Binding

Reflection은 언제 사용하나요?

1. 동적으로 클래스 로드 시

  • 코드 작성 시점에는 어떤 Class를 사용해야 할지 모르지만, Runtime에 Class를 가져와서 실행해야 하는 경우입니다.
  • 대표적으로 Spring의 Annotation 기능의 Reflection을 이용하여 프로그램 실행 도중 동적으로 클래스의 정보를 가져와 사용합니다.

2. 테스트 코드 작성 시

  • private 변수를 변경하고 싶거나 private method를 테스트할 경우입니다.

3. 자동 Mapping 구현

  • IDE 사용 시 자동 명령어 기능이 이에 해당합니다. Class 혹은 Method 목록들을 IDE가 먼저 확인하고 사용자에게 제공합니다.

Intellij의 자동 Mapping 기능

4. Jackson, GSON 등의 JSON Serialization

  • Reflection을 사용하여 객체 필드의 변수 명/어노테이션 명을 JSON Key와 Mapping 해주고 있습니다.

Reflection 사용 시점 중 가장 기대효과가 높은 부분은 바로 동적으로 Class를 사용해야 하는 경우입니다.

 

자동차 개발을 위해 자동차 모델에 따라 적합한 플러그인을 선택해 특정 기능들을 제공해야 하는 업무를 맡았다고 가정해봅시다.

특정 기능들은 아래와 같이 시스템 테마, 네비게이션, 컴포트 모드, 스포츠 모드로 이루어져 있습니다. 이러한 기능들은 기업 마다 다양하게 지원되고 있습니다.

[지원 가능한 전체 feature list]

# (1) 시스템 테마
v1_HYUNDAI_시스템_테마
v2_HYUNDAI_시스템_테마
v1_KIA_시스템_테마

# (2) 네비게이션
v1_TMAP_네비게이션
v2_TMAP_네비게이션
v3_TMAP_네비게이션
v1_KAKAO_네비게이션
v1_NAVER_네비게이션

# (3) 주행 모드
v1_HYUNDAI_컴포트_모드
v1_KIA_컴포트_모드

v1_HYUNDAI_스포츠_모드
v2_HYUNDAI_스포츠_모드
v1_KIA_스포츠_모드

이러한 기능들은 Model 혹은 OS별로 지원되는 라이브러리가 달라 아래와 같이 라이브러리 객체를 생성했다고 합시다.

public class ThemeLibFactory {

	private static final List<String> featureList = SystemProperties.get("lib.features");
    // List.of("v2_HYUNDAI_시스템_테마", "v3_TMAP_네비게이션", "v1_HYUNDAI_컴포트_모드", "v2_HYUNDAI_스포츠_모드");
    
    public BaseLibrary createLib(String featureName) {
    	switch(featureName) {
        	case "v1_HYUNDAI_시스템_테마"
            	return v1_HYUNDAI_시스템_테마();
        	case "v2_HYUNDAI_시스템_테마"
            	return v2_HYUNDAI_시스템_테마();
        	case "v1_KIA_시스템_테마"
            	return v1_KIA_시스템_테마();
        	case "v1_TMAP_네비게이션"
            	return v1_TMAP_네비게이션();
        	case "v2_TMAP_네비게이션"
            	return v2_TMAP_네비게이션();
        	case "v3_TMAP_네비게이션"
            	return v3_TMAP_네비게이션();
        	case "v1_KAKAO_네비게이션"
            	return v1_KAKAO_네비게이션();
        	case "v1_NAVER_네비게이션"
            	return v1_NAVER_네비게이션();
        	case "v1_HYUNDAI_컴포트_모드"
            	return v1_HYUNDAI_컴포트_모드();
        	case "v1_KIA_컴포트_모드"
            	return v1_KIA_컴포트_모드();
        	case "v1_HYUNDAI_스포츠_모드"
            	return v1_HYUNDAI_스포츠_모드();
        	case "v2_HYUNDAI_스포츠_모드"
            	return v2_HYUNDAI_스포츠_모드();
        	case "v1_KIA_스포츠_모드"
            	return v1_KIA_스포츠_모드();
            default
            	throw new IllegalArgumentException("Not supported feature library");
        }
	}

이후, 계속해서 새로운 버전의 기능들이 출시되면 일일히 case를 추가해줘야 하기 때문에 유지보수에 불편함이 있습니다.

 

따라서, Reflection을 이용하여 리팩토링을 아래와 같이 진행하였습니다.

public class ThemeLibFactory {
	private static final List<String> featureList =  SystemProperties.get("lib.features");
    // List.of("v2_HYUNDAI_시스템_테마", "v3_TMAP_네비게이션", "v1_HYUNDAI_컴포트_모드", "v2_HYUNDAI_스포츠_모드");
                                                            
	public static BaseLibrary getInstance(String featureName) throws Exception {
    	try {
            Class libClass = Class.forName(PACAKAGE_NAME + featureName);
            Constructor constructor = libClass.getConstructor();
            
            return (BaseLibrary) constructor.newInstance();
        } catch (Exception e) {
            throw new Exception("Not supported feature library: " + e);
        }
    }
}

Constructor 객체를 이용하여 Lib 객체를 생성하였습니다. 이 경우 Controller에서 Class의 이름을 넘겨주어야만 새로운 Library가 생기더라도 Factory 클래스의 수정 없이 유연하게 확장이 가능해집니다.

 

 

Reflection 사용 방법

Reflection을 통해 가져올 수 있는 정보는 아래와 같습니다.

 

  • Class / Interface
  • Constructor
  • Method
  • Field

해당 정보를 통해 객체 생성, 메서드 호출, 변수 값 변경을 할 수 있습니다.

Class / Interface

public static void main(String[] args) throws Exception{
	// 1. class를 알고 있을 경우
	Class car = Car.class;
	
	// 2. class 이름만 알고 있을 경우
	Class car = Class.forName("com.reflection.test.Car");
	
	// 3. Default 생성자를 이용한 객체 생성
	Car realCar = car.newInstance();
	
	// 4.class에 구현된 interface 확인
	Class[] interfaces = car.getInterfaces();
}

Constructor

public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
    
    // 1. 인자가 없는 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor();
    
    // 2. String 인자를 가진 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor(String.class);
    
    // 3. 모든 생성자 가져오기
    Constructor constructors[] = car.getDeclaredConstructors();
    
    // 4. public 생성자만 가져오기
    Constructor constructors[] = car.getConstructors();
    // public com.reflection.test.Car()
	// public com.reflection.test.Car(java.lang.String)
    
    // 5. 생성자를 이용한 객체 생성
    Car realCar = constructor.newInstance();
}

Method

public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
  
    // 1. 인자가 없는 method 가져오기
    Method method = car.getDeclaredMethod("move");
    
    // 2. String 인자를 가진 method 가져오기
    Method method = car.getDeclaredMethod("move", String.class);
    
    // 3. 모든 method 가져오기
    Method methods[] = car.getDeclaredMethods();
    
    // 4. 상속받은 method와 public method 가져오기
    Method methods[] = car.getMethods();
	// public void com.reflection.test.Car.move()
	// public void com.reflection.test.Car.move(java.lang.String)
    
    // 5. method 호출
    Class realCar = car.newInstance();
    method.invoke(realCar, /*인자*/);
    
    // 6. 접근 제한자를 무시한 method 호출.
    method.setAccessible(true);
    method.invoke(realCar, /*인자*/);
}

Field

public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
    
    // 1. car 객체에서 name 에 해당하는 field 가져오기
    Field field = car.getDeclaredField("name");
    
    // 2. car + car super 객체를 포함하여 name에 해당하는 field 가져오기
    Field field = car.getField("name");
    
    // 3. car 객체에 선언된 모든 field 가져오기
    Field[] fields = car.getDeclaredFields();
    // private java.lang.String com.reflection.test.Car.name
	// public java.lang.Integer com.reflection.test.Car.type
    
    // 4. car + car super 객체의 모든 public field 가져오기
    Field[] fields = car.getFields();
	// public java.lang.Integer com.reflection.test.Car.age
}

+ 필드 값 변경

public static void main(String[] args) throws Exception {
    Class class = Class.forName("com.reflection.test.Car");
    Constructor constructor = class.getConstructor()
    Car car = constructor.newInstance()
        
    Field field = car.getField("name");
    
    // 1. public field 일 경우
    field.set(car, "아반떼");
    
    // 2. private field 일 경우
    field.setAccessible(true);
    field.set(car, "아반떼");
}

 

 

Reflection의 장단점

장점

  • 확장성: if/else 조건문을 난발하지 않고, Reflection을 이용해서 재사용 가능한 컴포넌트로 만들 수 있습니다.
  • Class 브라우저 및 시각적 개발 환경을 제공: Reflection을 통해 Method, Property, Constructor를 미리 파악함으로써 사용할 정보를 열거하여 시각적으로 개발 환경을 구축할 수 있습니다.
  • 디버거 및 테스트 도구: 디버거는 개인 property, Method, Constructor를 검사할 수 있어야 합니다. 테스트 장치는 Reflection을 이용하여 클래스에 정의된 발견 가능한 세트 API를 체계적으로 호출하여 테스트에서 높은 수준의 코드 커버리지를 보장할 수 있습니다.
  • 라이브러리 파악: Java에서 지원하는 라이브러리가 아닌 특정 기업의 라이브러리를 사용하는 경우 해당 라이브러리에 존재하는 클래스 및 메서드를 분석할 수 있습니다.

단점

  • 컴파일 시점에 타입 확인이 불가능하기 때문에 컴파일 시에 타입 확인이나 예외 검사를 할 수 없습니다.
  • 클래스, 메서드, 필드를 접근하여 직접 이용하기 때문에 객체 지향 프로그래밍의 특징인 추상화를 위반합니다.
  • 컴파일 에러가 아닌 런타임 시, 에러가 발생하기 때문에 코드 운용에 위험이 있습니다.

'Language > JAVA' 카테고리의 다른 글

[JAVA]중첩/내부 클래스  (0) 2024.09.23
[JAVA]static 파헤치기  (0) 2024.09.23
[JAVA]Thread란?  (0) 2024.09.09
[JAVA]Object가 아닌 Objects 클래스  (0) 2024.09.03
[JAVA]equals()와 hashCode()를 같이 재정의 하는 이유  (0) 2024.08.30
공지사항
최근에 올라온 글
Total
Today
Yesterday