티스토리 뷰

Programming/Java

Reflection

Albothyl 2019. 6. 18. 20:32

1. Reflection 이란?

- Java에서 지원하는 기능으로, 실행중인 Java Application에서 동적으로 Class의 Metadata를 읽어와 특정 정보를 획득하거나, Instance를 다룰 수 있다. 대표적으로 Spring에서는 @Component가 붙은 Class의 Instance를 생성하거나, 제 3의 Instance를 DI해줄 때 사용된다. Spring에서 이런 작업들이 수행될 때 접근 제한자가 private이어도, 해당 메소드를 실행할 수 있는 이유가 바로 Reflection을 사용하기 때문이다.

 

2. 자주 사용될 수 있는 Class Metadata

//Class
String     simpleName = clazz.getSimpleName();
Class<?>   superclass = clazz.getSuperclass();
Class<?>[] interfaces = clazz.getInterfaces();

//Annotation
for (Annotation declaredAnnotation : clazz.getDeclaredAnnotations()) {
	Class<? extends Annotation> annotationType = declaredAnnotation.annotationType();
}

//Constructor
for (Constructor<?> declaredConstructor : clazz.getDeclaredConstructors()) {
	Class<?>[]     parameterTypes        = declaredConstructor.getParameterTypes();
	Object         newInstance           = declaredConstructor.newInstance();
	Annotation[]   declaredAnnotations   = declaredConstructor.getDeclaredAnnotations();
	Class<?>[]     exceptionTypes        = declaredConstructor.getExceptionTypes();
	Type[]         genericParameterTypes = declaredConstructor.getGenericParameterTypes();
	String         name                  = declaredConstructor.getName();
	Annotation[][] parameterAnnotations  = declaredConstructor.getParameterAnnotations();
	int            parameterCount        = declaredConstructor.getParameterCount();
}

//Field
for (Field declaredField : clazz.getDeclaredFields()) {
	Class<?>     type                = declaredField.getType();
	Annotation[] declaredAnnotations = declaredField.getDeclaredAnnotations();
	String       name                = declaredField.getName();
}

//Method
for (Method declaredMethod : clazz.getDeclaredMethods()) {
	Annotation[]   declaredAnnotations   = declaredMethod.getDeclaredAnnotations();
	Class<?>       declaringClass        = declaredMethod.getDeclaringClass();
	Class<?>[]     exceptionTypes        = declaredMethod.getExceptionTypes();
	Type[]         genericParameterTypes = declaredMethod.getGenericParameterTypes();
	String         name                  = declaredMethod.getName();
	Annotation[][] parameterAnnotations  = declaredMethod.getParameterAnnotations();
	int            parameterCount        = declaredMethod.getParameterCount();
	Class<?>       returnType            = declaredMethod.getReturnType();
}

3. Reflection을 활용한 Autowired DI Sample Code

@Getter
@ToString
@AllArgsConstructor
public class Board {

	private String name;

	public void modify(Board newBoard) {
		this.name = newBoard.getName();
	}
}
@Slf4j
@Service
public class BoardFinder {

	public Board find() {
		Board board = new Board("basicBoard");
		log.info("#### BoardFinder.find - output: {}", board);

		return board;
	}
}
@Slf4j
@Service
public class BoardModifier {

	@Autowired
	private BoardFinder boardFinder;

	public void modify(Board newBoard) {
		log.info("#### BoardModifier.modify - input: {}", newBoard);

		Board board = boardFinder.find();

		board.modify(newBoard);

		log.info("#### BoardModifier.modify - output: {}", board);
	}
    
	@PostConstruct
	public void init() {
		log.info("#### BoardModifier @PostConstruct");
	}
}
import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.Map;
import java.util.Optional;

public class BoardReflection {

	private Map<String, Object> singletonBeanContext = Maps.newHashMap();

	public static void main(String[] args) throws Exception {
		BoardReflection boardReflection = new BoardReflection();

		String classFullPath = "com.project.kanban.application.board.BoardModifier";
		boardReflection.registerSingletonBean(classFullPath);

		BoardModifier boardModifier = (BoardModifier) boardReflection.getBean("BoardModifier");
		boardModifier.modify(new Board("newBoard"));
	}
    
	public Object getBean(String beanName) throws ClassNotFoundException {
		return Optional.ofNullable(this.singletonBeanContext.get(beanName)).orElseThrow(() -> new ClassNotFoundException(beanName + " is not found"));
	}

	private void registerSingletonBean(String classFullPath) throws Exception {
		Class<?> clazz = Class.forName(classFullPath);
		registerSingletonBean(clazz);
	}

	private void registerSingletonBean(Class<?> clazz) throws Exception {
		if (isComponent(clazz)) {
			Object singletonBean = createSingletonBean(clazz);
			singletonBeanContext.put(singletonBean.getClass().getSimpleName(), singletonBean);
		}
	}

	//Spring에서는 Project를 Scan하여 BeanDefinition을 생성하고, 마지막 초기화 과정에서 객체 생성 및 DI를 진행한다.
	private Object createSingletonBean(Class<?> clazz) throws Exception {

		//Get Class Metadata Information
		Field[] declaredFields = clazz.getDeclaredFields();
		Method[] declaredMethods = clazz.getDeclaredMethods();

		//Create Class Instance
		Object clazzInstance = clazz.getDeclaredConstructor().newInstance();

		//Extract and Create Autowired Target
		for (Field declaredField : declaredFields) {
			declaredField.setAccessible(true);

			for (Annotation fieldDeclaredAnnotation : declaredField.getDeclaredAnnotations()) {
				if (fieldDeclaredAnnotation.annotationType() ==  Autowired.class) {
					Class fieldType = declaredField.getType();

					Object autowiredInstance = createSingletonBean(fieldType);

					declaredField.set(clazzInstance, autowiredInstance);
				}
			}

			declaredField.setAccessible(false);
		}

		for (Method declaredMethod : declaredMethods) {
			for (Annotation methodDeclaredAnnotation : declaredMethod.getDeclaredAnnotations()) {
				if (methodDeclaredAnnotation.annotationType() ==  PostConstruct.class) {
					declaredMethod.invoke(clazzInstance);
				}
			}
		}

		return clazzInstance;
	}

	private boolean isComponent(Class<?> clazz) {
		for (Annotation clazzDeclaredAnnotation : clazz.getDeclaredAnnotations()) {
			if (clazzDeclaredAnnotation.annotationType() == Documented.class) {
				continue;
			}

			if (clazzDeclaredAnnotation.annotationType() == Retention.class) {
				continue;
			}

			if (clazzDeclaredAnnotation.annotationType() == Target.class) {
				continue;
			}

			if (clazzDeclaredAnnotation.annotationType() == Component.class) {
				return true;
			}

			if (clazzDeclaredAnnotation.annotationType().getDeclaredAnnotations().length - 3 > 0) {
				return isComponent(clazzDeclaredAnnotation.annotationType());
			}
		}

		return false;
	}
}

Reflection은 접근 제한자를 무시, Metadata에 정보를 획득, Field값 추가, Instance 생성 등 매우 강력하면서도 위험한 기능이다. 
하지만 매우 느리다. Runtime에서 동적으로 Reflection을 사용할 수 있지만, 많은양의 작업에는 부적합 하다. 이러한 특징으로 인해 Application이 시작될 때 진행하는 초기화 작업에서 사용하는것이 좋다.

'Programming > Java' 카테고리의 다른 글

ClassPath  (0) 2020.05.02
상속과 this  (0) 2020.01.12
LocalDateTime  (0) 2019.03.11
JMX - mBean  (2) 2018.11.15
StringBuffer, StringBuilder  (0) 2016.08.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함