똑같은 삽질은 2번 하지 말자

Java Reflection API(리플렉션) 본문

Java

Java Reflection API(리플렉션)

곽빵 2020. 9. 21. 11:28

리플렉션 이란? 

객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법

자바의 리플렉션(Reflection)은 클래스, 인터페이스, 메소드들을 찾을 수 있고, 객체를 생성하거나 변수를 변경할 수 있고 메소드를 호출할 수도 있습니다. Reflection은 자바에서 기본적으로 제공하는 API입니다. 사용 방법만 알면 라이브러리를 추가할 필요 없이 사용할 수 있습니다.

 

리플렉션의 시작은 Class<T>

Class<T>에 접근하는 방법

  • 모든 클래스를 로딩 한 다음 Class<T>의 인스턴스가 생긴다. “타입.class”로 접근할 수 있다.

  • 모든 인스턴스는 getClass() 메소드를 가지고 있다. “인스턴스.getClass()”로 접근할 수 있다.

  • 클래스를 문자열로 읽어오는 방법

    • Class.forName(“FQCN”)

    • 클래스패스에 해당 클래스가 없다면 ClassNotFoundException이 발생한다.

Class<T>를 통해 할 수 있는 것

  • 필드 (목록) 가져오기

  • 메소드 (목록) 가져오기

  • 상위 클래스 가져오기

  • 인터페이스 (목록) 가져오기

  • 애노테이션 가져오기

  • 생성자 가져오기

대표적으로 사용되어지고 있는 곳이 Spring의 DI이다,

BeanFactory가 App 실행 후, 객체가 호출될때, @Component가 붙은 객체들을 생성하는데 이때 쓰이는게 Reflection API

 

그럼 내가 정의한 애노테이션을 Spring 처럼 객체생성해서 주입까지 해보자

 

애노테이션 정의

@Retention(RetentionPolicy.RUNTIME) // 해당 애노테이션을 언제까지 유지할 것인가? 소스, 클래스, 런타임
public @interface Inject {
}

 

@Inject 라는 애노테이션이 붙어있으면 리플렉션 기법으로 인스턴스를 만들어 주겠다.

public class ContainerService {
	public static <T>T getObject(Class<T> classType) {
		T instance =  createInstance(classType);
		Arrays.stream(classType.getDeclaredFields()).forEach(f -> {
			if(f.getAnnotation(Inject.class) != null) {
				Object fieldInstance = createInstance(f.getType());
				f.setAccessible(true);
				try {
					f.set(instance, fieldInstance);
				} catch (IllegalArgumentException | IllegalAccessException e) {
					throw new RuntimeException();
				}
			}
		});
		return instance;
	}
	public static<T> T createInstance(Class<T> classType) {
		try {
			return classType.getConstructor(null).newInstance();
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
				| NoSuchMethodException | SecurityException e) {
			throw new RuntimeException();
		}
	}
}

테스트

public class BookService {
	@Inject
	BookRepository bookRepository;
	
}

public class BookRepository {

}


public class ContainerServiceTest {

	@Test
	public void getObject() {
		BookRepository bookRepository = ContainerService.getObject(BookRepository.class);
		assertNotNull(bookRepository);
	}
	@Test
	public void getObject_BookService() {
		BookService bookService = ContainerService.getObject(BookService.class);
		assertNotNull(bookService);
		assertNotNull(bookService.bookRepository);
	}

}

 

 

Reflection의 특징

 

 

1. 확장성 특징

애플리케이션은 정규화된 이름을 사용하여 확장성 객체의 인스턴스를 생성하여 외부 사용자 정의 클래스를 사용할 수 있습니다.

 

 

2. 클래스 브라우저 및 시각적 개발 환경을 제공

클래스 브라우저는 클래스의 Method, Property, Constructor를 열거할 수 있어야 합니다. 시각적 개발 환경은 개발자가 올바른 코드를 작성하는데 도움이 되도록 Reflection에서 사용할 수 있는 형식 정보를 사용하면 도움이 됩니다.

 

 

3. 디버거 및 테스트 도구

디버거는 개인 Property, Method, Constructor를 검사할 수 있어야 합니다. 테스트 장치는 Reflection을 사용하여 클래스에 정의된 발견 가능한 세트 API를 체계적으로 호출하여 테스트에서 높은 수준의 코드 커버리지를 보장합니다.

 

Reflection은 강력한 도구이지만, 무분별하게 사용해서는 안된다. Reflection을 사용 하지 않고 수행 가능하다면,

사용하지 않는 것이 좋다. Reflection을 통해 코드에 접근할 때는 다음 사항을 염두에 두어야 합니다

 

Reflection의 주의사항 및 단점.

1. Performance의 오버헤드

Reflection에는 동적으로 해석되는 유형이 포함되므로, 특정 JVM 최적화를 수행할 수 없습니다.

따라서 Reflection 작업이 비 Reflection 작업보다 성능이 떨어지며,

성능에 민감한 애플리케이션에서 자주 호출되는 코드엔 사용하지 않아야 합니다.

 

2. 보안 제한 사항

Reflection에는, 시큐리티 매니저의 실행 시에 존재하지 않는 실행 시 액세스 권한이 필요합니다.

이것은 애플릿과 같이 제한된 보안 컨텍스트에서 실행되어야 하는 코드에 대한 중요한 고려 사항입니다.

 

 

3. 캡슐화 저해

Reflection은 private한 Property및 Method에 액세스하는 것과 같이 비 Reflection 코드에서 작동하지 않는 코드를 수행할 수 있으므로, Reflection을 사용하면 예기치 않은 부작용이 발생하여 코드 기능이 저하되고 이식성이 손상될 수 있습니다. 또한 Reflection은 추상화를 깨뜨려 플랫폼 업그레이드 시 동작이 변경될 수 있습니다.

 

Comments