안녕하세요, Brad입니다. 오늘은 Java의 고급주제 중 하나인 Reflection에 대해 배웠는데요. 정리해볼게요.
Java Reflection (발표 : soop)
Java
Relection없이는 자바는 동적으로 클래스를 생성할 수 없습니다.
동적으로 객체를 만든는게 왜 중요할까요? 반대로 정적인 것은 뭘 의미할까요?
- 동적으로 객체를 만든다는 것은 컴파일 시에
new
를 사용해서 일반적으로 객체 생성하는 것과 다르게 런타임 시에 클래스 경로를 통해 객체를 만드는 것을 말합니다.
- 동적으로 객체를 만든다는 것은 컴파일 시에
자바는 인터프리터 언어가 아닌 컴파일 언어입니다.
인터프리터 언어와 컴파일 언어는 뭐가 다를까요?
- 인터프리터 언어는 한번에 기계어로 바꾸는 컴파일 과정 없이 바로바로 소스코드를 읽어나가면서 프로그램을 가동시키는 방법입니다.
- 그렇기 때문에 속도는 느리지만 기존 프로그램 뒤에서 구동을 지원함으로 변동이 자주있는 프로그램에 유용합니다.
- 인터프리터 언어의 예로는 Javascript, PHP, Python 등이 있습니다.
클래스 타입을 알지 못해도, 컴파일된 바이트 코드를 통해 역으로 클래스를 알 수 있습니다. 객체를 통해 클래스의 정보를 분석합니다.
컴파일된 바이트 코드?
자바 바이트 코드는 자바 프로그램의 컴파일 결과물입니다. 기계에 독립적인 중간적인 성격이죠. 중간적인 성격인 이유는 자바 바이트 코드는 해당 기계어가 아니고 JVM으로 처리될 수 있도록 만들어진 것이기 때문입니다.
예를들어 아래와 같이 해당 클래스의 모든 메서드들을 불러올 수 있습니다.
Class<Bike> class = Bike.class;
Method[] methods = class.getMethods();
// Method[] methods = class.getDeclaredMethods();
for (Method method : methods) {
log.debug(method.getNames());
method.invoke(class.newInstance()); // 실행
}
2번째 줄을 통해 여러 메서드들을 불러보면 알겠지만 해당 클래스에 정의된 것 이외에 다른 메서드들도 다 부르는데요. 그건 상속구조, 부모 메서드 등 다 알아서 불러오기 때문입니다.
Bike.class는 어떻게 읽어올 수 있을까요?
사실 위와 같이 클래스 이름을 알고있지 않을 때 해당 클래스의 이름을 불러오는 것이 중요한 것이죠. 런타임시에 리플렉션이 실행될 때는 컴파일된 자바 바이트코드에서 해당 클래스의 패키지 정보를 가지고 있습니다. 그렇기 때문에 실제 Class<?> clazz = Class.forName("next.optional.User");
와 같이 패키지 정보로 클래스를 불러올 수 있습니다.
런타임 때 동적으로 클래스 정보를 읽어올 수 있다는 점에서 유용합니다.
출처 : http://www.java67.com/2013/02/difference-between-jit-and-jvm-in-java.html
위 부분은 JVM에 대해 공부를 한 다음에 다시 살펴보도록 하겠습니다.
주의할 점
private 접근제어자도 접근가능하기 때문에 유의하여 사용하여야 합니다.
- 참고) private 필드에
@Autowired
할 때 Reflection을 사용했기 때문에 빈 주입이 가능했습니다
- 참고) private 필드에
부가 설명 by Pobi
Spring Framework에서 활용되고 있는 부분
Mapping에서 객체를 만들어 낼 때
- 처음에 서버가 뜰 때 각 매핑 메서드에 대한 정보가 Map의 key - value값으로 가지고 있습니다.
- 그리고 요청이 들어올 때 Map의 정보를 이용하여 동적으로 객체를 생성하여 setter를 이용하여 값을 대입해 넣습니다.
Bean 주입을 할 때
- 객체 정의만 했는데
@Resource
나@Autowired
등으로 빈이 다 주입이 됩니다.
- 객체 정의만 했는데
클래스로더
바이트 정보 내에 패키지 이름을 가지고 클래스명을 가져올 수 있습니다.
Class<?> clazz = Class.forName("next.optional.User"); clazz.newInstance();
위와 같은 형태가 동적으로 클래스를 만드는것입니다. 런타임 시에 클래스를 생성합니다.
User user = new User();
이것은 정적으로 클래스를 만드는 것인데요. 컴파일 시에 클래스를 생성합니다.
실습
수업시간에 몇 가지 요구사항을 수행하였습니다. 관련 메서드는 검색을 통해 충분히 찾을거라 생각합니다. 소스코드에 대해서만 기록해둘게요.
- 요구사항1 - 클래스 정보 출력
@Test
public void showClass() {
Class<Question> clazz = Question.class;
logger.debug(clazz.getName());
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
logger.debug("메서드 : {}", method.getName());
}
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
logger.debug("생성자 : {}", constructor.getName());
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
logger.debug("필드 : {}", field.getName());
}
}
- 요구사항2 - test로 실행하는 메서드 실행
public class Junit3TestRunner {
private static final Logger log = getLogger(Junit3TestRunner.class);
@Test
public void run() throws Exception {
Class<Junit3Test> clazz = Junit3Test.class;
// TODO Junit3Test에서 test로 시작하는 메소드 실행
Method[] methods = clazz.getDeclaredMethods();
methods = Arrays.stream(methods)
.filter(method -> startWithTest(method.getName()))
.toArray(Method[]::new);
for (Method method : methods) {
log.debug("test로 시작하는 메서드 : {}", method);
method.invoke(clazz.newInstance()); // 해당 메서드 실행
}
}
private boolean startWithTest(String name) {
return name.startsWith("test");
}
}
참고) stream에서 toArray()
메서드를 사용할 때 자꾸 컴파일 에러가 발생했었는데요. 기본적으로 toArray()
는 Object타입으로 반환을 합니다. 이 상태에서 캐스트를 하더라도 오류가 발생하더라구요. 따라서 toArray()
메서드 안에 해당 클래스를 new
를 통해 생성하면 해결할 수 있습니다. 자바에서도 권장하는 방식이라고 하더라구요.
- 요구사항3 - @Test 애노테이션 메소드 실행
리플렉션을 사용하면 Annotation이 정의된 부분도 가져올 수 있습니다!
@Test
public void run() throws Exception {
Class<Junit4Test> clazz = Junit4Test.class;
// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Method[] methods = clazz.getMethods();
List<Method> annotatedMethods = Arrays.stream(methods)
.filter(method -> method.isAnnotationPresent(MyTest.class))
.collect(Collectors.toList());
for (Method method : annotatedMethods) {
method.invoke(clazz.newInstance());
}
}
- 요구사항4 - private field에 값 할당
private 접근제어자에 접근하여 값을 할당하는 것도 가능합니다.
@Test
public void fieldAccess() throws Exception {
Class<Student> student = Student.class;
Field nameField = student.getDeclaredField("name");
Field ageField = student.getDeclaredField("age");
nameField.setAccessible(true);
ageField.setAccessible(true);
Student thisStudent = new Student(); // (주의)객체를 만들어줘야함!!
nameField.set(thisStudent, "브래드와 숲");
ageField.set(thisStudent, 20);
logger.debug("student : {}", thisStudent);
}
- 요구사항5 - 인자를 가진 생성자의 인스턴스 생성
사실 요구사항4에서 객체를 new
를 이용하여 직접 만들어주었지만 이 또한 동적으로 생성해야겠죠. 생성자를 이용하여 다음과 같이 만들 수 있습니다.
@Test
public void makeConstructor() throws Exception {
Class<?> clazz = Class.forName("next.optional.User");
for (Constructor declaredConstructor : clazz.getDeclaredConstructors()) {
Object user = declaredConstructor.newInstance("브래드", 20);
logger.debug("user : {}", user);
}
}
'TIL' 카테고리의 다른 글
서버 N대 성능개선 (0) | 2019.01.22 |
---|---|
Today's Dev Notes(2019-01-20) (0) | 2019.01.20 |
Today's Dev Notes(2019-01-16) (0) | 2019.01.16 |
Today's Dev Notes(2019-01-15) (0) | 2019.01.15 |
Today's Dev Notes(2019-01-14) (0) | 2019.01.14 |