2. Java Reflection API란?
- 클래스의 정보를 얻을 수 있는 기능을 제공. java.lang.reflect패키지.
- 실행 중에 객체 생성, 메소드 호출 등을 가능하게 한다. (동적)
* 정적 : 컴파일 때 결정
* 동적 : 실행중에 결정
- Reflection API를 이용한 예제 코드
Class carClass = Car.class; // Car 클래스의 Class 객체 얻기 -> 설계도 객체
Car car = (Car) carClass.newInstance(); // Car클래스의 객체를 생성 = Car car = new Car(); 보다 유연
Method[] methodArr = carClass.getDeclaredMethods(); // 클래스에 선언된 메소드 얻기
Field[] mvArr = carClass.getDeclaredFields(); // 클래스에 선언된 필드(iv,cv) 얻기
Method method = carClass.getMethod("setEngine","Engine.class"); // setEngine(Engine engine)
method.invoke(car, new Engine()); // car.setEngine() 호출
Annotation[] annoArr = mv.getDeclaredAnnotations(); // 필드에 붙은 어노테이션 얻기
5 - 이름이 setEngine이고 매개변수가 Engine클래스인 메소드의 정보를 얻어서 메소드의 참조 변수에 저장하고,
invoke 메소드를 통해 호출하면 car.setEngine()을 호출한 것과 똑같다.
3. Class 객체 -> '설계도' 객체로 존재
- Class클래스의 객체. 클래스당 1개만 존재
- 해당 클래스의 정보 조회, 객체 생성 등의 기능 제공
- 클래스 파일(.*class)이 메모리에 로드될 때 생성
Card.java -(컴파일)-> Card.class(파일 설계도) -> ClassLoader -> Class객체
java를 실행하면 파일 형태의 설계도를 메모리에 올린다. 그 역할을 ClassLoader가 해주며 그 파일이 안전한지, 악성코드가 없는지 검사한다. 올리면서 Class객체를 만든다.(객체 형태의 설계도)
설계도가 파일 형태였었는데 객체 형태로 만들어진 것이다.
파일에 있는건 우리가 쓰기 쉽지않다. (불편)
반면에, 객체는 사용하기 편리하다. 메소드들을 다 가지고 있음!
Class classObj = new Car().getClass(); // 1. 객체에서 얻는 방법
Class classObj = Card.class(); // 2. 클래스 리터럴로 얻는 방법
Class classObj = Class.forName("Card"); // 3. 클래스 이름으로 얻는 방법(Card앞에 패키지도 적어야함)
<실습>
- ReflectionTest 클래스 생
package com.fastcampus.ch3.di1;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Car car = new Car();
Class carClass = car.getClass(); // 1. 객체로부터 Class객체(설계도) 얻기
carClass = Car.class; // 2.객체 리터럴로부터 Class객체 얻기
carClass = Class.forName("com.fastcampus.ch3.di1.Car");
// 1. 설계도 객체로부터 객체 생성하기
Car car2 = (Car)carClass.newInstance();
System.out.println("car2 = " + car2);
// 2. 클래스에 선언된 멤버변수(field)와 method목록 열기
Field[] mvArr = carClass.getDeclaredFields();
// Method[] methodArr = carClass.getDeclaredMethods();
Method[] methodArr = carClass.getMethods();
for(Field mv : mvArr) System.out.println(mv.getName());
for(Method method : methodArr) System.out.println(method.getName());
}
}

wait나 equals는 조상인 Object가 가지고 있는 것이다.
- Car클래스에 멤버 변수를 더 추가해보자.
class Car {
Engine engine;
Door door;
@Override
public String toString() {
return "Car{" +
"engine=" + engine +
", door=" + door +
'}';
}
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Door getDoor() {
return door;
}
public void setDoor(Door door) {
this.door = door;
}
}
이후 다시 코드를 실행해보면

방금 생성한 iv와 메소드들이 나오는 것을 볼 수 있다.
- ReflectionTest에서 setEngine 메소드를 호출해보자.
다음 코드를 추가해준다음 실행한다.
// 정보를 메소드 객체에 담아서 반환
Method method = carClass.getMethod("setEngine", Engine.class);
// 호출
method.invoke(car, new Engine()); // car.setEngine(new Engine());
System.out.println("car = " + car);

Engine을 넣어줬으니 아까와 다르게 Engine이 들어간 것을 볼 수 있다.
- Field에 set을 붙여서 호출하기
mv.getName() -> engine이걸 첫 글자를 대문자로 만드는것을 String이 제공한다.
StringUtils.capitalize(mv.getName())을 통해 첫 글자를 대문자로 만들어준다.
// 3. mv에 set을 붙여서 setter를 호출하기
for(Field mv : mvArr) {
System.out.println("mv = " + mv);
String methodName = "set" + StringUtils.capitalize(mv.getName()); // "set" + "Engine" = "setEngine"
System.out.println("methodName = " + methodName);
method = carClass.getMethod(methodName, mv.getType()); // carClass.getMethod("setEngine", Engine.class);
method.invoke(car, mv.getType().newInstance()); // car.setEngine(new Engine()), car.setDoor(new Door())
}
System.out.println("car = " + car);

아까와 달리 door도 null이 아니다. 반복문에 의해 setEngine()뿐만 아니라 setDoor()도 호출이 된 것이다.
- Car의 멤버변수 Engine에만 @Autowired를 붙여보자.
class Car {
@Autowired
Engine engine;
Door door;
- 모든 iv를 돌면서 거기 붙은 어노테이션을 반복문으로 돌리는 것이다.
for(Field mv : mvArr){
Annotation[] annoArr = mv.getDeclaredAnnotations();
for(Annotation anno : annoArr) {
System.out.println("mv.getName() = " + mv.getName());
System.out.println("anno.annotationType().getSimpleName() = " + anno.annotationType().getSimpleName());
System.out.println(anno.annotationType() == Autowired.class);
}
}

cf) Autowired가 붙어있으면 Bean을 주입해준다.
- reflection API를 이용해서 Autowired가 붙어 있는 필드에만 setter를 호출(3+4번째 합친 것)
for(Field mv : mvArr) {
Annotation[] annoArr = mv.getDeclaredAnnotations();
for(Annotation anno : annoArr) {
System.out.println("mv.getName() = " + mv.getName());
if(anno.annotationType() == Autowired.class){
// setter 호출
String methodName = "set" + StringUtils.capitalize(mv.getName()); // "setEngine";
method = carClass.getMethod(methodName, mv.getType());
method.invoke(car, mv.getType().newInstance());// car.setEngine(new Engine())
}
}
}
System.out.println("car = " + car);

@Autowired가 붙어 있는 변수가 있는 것만 null이 아닌 객체가 들어가는 것을 확인할 수 있다.
* 동적 코드 작성을 위해 반복 학습!!
'SpringBoot' 카테고리의 다른 글
| ch3 04. Bean과 ApplicationContext (0) | 2023.07.30 |
|---|---|
| ch3 03. Spring DI의 원리(2) (0) | 2023.07.29 |
| ch3 01. Spring DI의 원리(1) (0) | 2023.07.18 |
| ch2 16. thymeleaf 사용하기 (0) | 2023.07.18 |
| ch2 15. forward와 redirect - 실습 (0) | 2023.07.14 |