- 스프링은 어떻게 실행시점에 빈을 주입할 수 있는 건가요?
- JPA의 Entity는 왜 꼭 기본 생성자를 가져야만 할까요?
리플렉션
컴파일러가 자바 코드를 바이트 코드로 바꿔줌 -> 클래스 로더는 바이트코드를 읽어 JVM 내 메모리 영역 저장
리플렉션 : JVM영역에 저장된 클래스의 정보를 꺼내와서 필요한 정보들(생성자, 필드, 메서드)을 가져와 사용하는 기술
Class : 실행중인 자바 어플리케이션의 클래스와 인터페이스의 정보를 가진 클래스
자동으로 생성된 Class 객체 가져오는 방법
getMethods
vs getDeclaredMethods
주의
getMethods
: 상위 클래스와 상위 인터페이스에서 상속한 메서드를 포함하여 public인 메서드들을 모두 가져온다. getDeclaredMethods
: 접근 제어자와 관계 없이 상속한 메서드를 제외하고 직접 클래스에서 선언한 메서드들을 모두 가져온다.public class Dog {
private static final String CATEGORY = "동물" ;
private String name;
public in age;
private Dog() {
this.name = "누렁이";
this.age = 0;
}
public Dog(final String name) {
this.name = name;
this.age = 0;
}
public Dog(final String name, final int age) {
this.name = name;
this.age = age;
}
}
// Class 객체로 가져옴
Class<?> clazz = Class.forName("org.example.Dog");
// 생성자를 Constructor로 가져옴
Constructor<?> constructor1 = class.getDeclaredConstructor();
Constructor<?> constructor2 = class.getDeclaredConstructor(String.class);
Constructor<?> constructor3 = class.getDeclaredConstructor(String.class, int.class);
Object dog1 = constructor1.newInstance();
setAceessible
메서드를 이용하면 접근할 수 있다.constructor1.setAccessible(true);
Object dog1 = constructor1.newInstance();
Object dog2 = constructor2.newInstance("호두");
Object dog3 = constructor3.newInstance("호두", 5);
Object dog = constructor.newInstance("호두", 5);
// 클래스에 정의된 필드를 Field 객체로 받아올 수 있음
Field[] fields = clazz.getDeclaredFields();
for ( Field field : fields) {
// 필드도 private 접근제어자가 존재할 수도 있기에 설정
field.setAccessible(true);
System.out.println(field);
// 원하는 인스턴스를 넣으면 인스턴스가 가진 field의 값을 가져올 수 있음
System.out.println("value : " + field.get(dog));
System.out.println("-------------------------");
}
// name 필드를 가져옴
Field field = clazz.getDeclaredField("name");
// private 필드이기에 설정
field.setAccessible(true);
System.out.println("기존: " + field.get(dog));
field.set(dog, "땅콩");
System.out.println("변경: " + field.get(dog));
public class Dog {
...
private void speak(final String sound, final int count) {
System.out.println(sound.repeat(count));
}
public void eats() {
System.out.println("사료를 먹습니다. ");
}
public int getAge() {
return age;
}
}
// Method 타입의 객체로 받아오기
Method[] methods = class.getDeclaredMethods();
for (Method method : methods) {
method.setAccessible(true);
System.out.print(method);
System.out.println("\n-------------------");
}
Method method = class.getDeclaredMethod("speak", String.class, int.class);
method.setAccessible(true);
// invoke 메서드로 호출
method.invoke(dog, "멍멍!", 5);
}
주로 프레임워크나 라이브러리에서 사용함
JPA, Jackson, Mockito, JUNIT, intelliJ의 자동완성 기능 등에서 사용됨
프레임워크나 라이브러리에서 객체의 기본생성자가를 요구하는 경우가 많은데 왜 필요할까?
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
protected Member() {
}
// 생략 ..
}
기본 생성자로 객체를 생성하고 필드를 통해 값을 넣어주는 것이 가장 간단한 방법이기 때문
기본 생성자가 없다면 어떤 생성자를 사용할 지 고르기 어렵다.
public Member(final String name) {
this.name = name;
}
public Member(final String name, final String email) {
this.name = name;
this.email = email;
}
// 생략 ..
public Dog(int age) {
this.age = age + 1;
}
public Member(final String displayName, final String myEmail, final String iamgeUrl) {
this.name = displayName;
this.email = myEmail;
this.url = imageUrl;
}
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
public Member() {
}
// 생략 ..
}
public class OrderService {
@Autowired
OrderRepository orderRepository;
}
public class OrderRepository {
Map<Integer, Order> orderIdToOrderMap = new HashMap<>();
public Order getById(Integer orderId) {
return orderIdToOrderMap.get(orderId);
}
}
@Target(ElementType.FIELD)
// 런타임 시 호출하는 어노테이션
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
// 의존관계 주입 핵심 담당
public class ApplicationContext {
public static <T> T getInstance(Class<T> clazz) throws Exception {
T instance = createInstance(clazz); // 인스턴스 생성
Field[] fields = clazz.getDeclaredFields();
for( Field field : fields) {
// @Autowired 어노테이션 붙어있다면
if (field.getAnnotation(Autowired.class) != null) {
// 필드 타입에 맞는 인스턴스를 생성
Object fieldInstance = createInstance(field.getType());
field.setAccssible(true);
// 필드 인스턴스를 해당 필드에 주입
field.set(instance, fieldInstance);
}
}
return instance;
}
private static <T> T createInstance(Class<T> clazz) throws Exception {
return clazz.getConstructor(null).newInstance();
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext applicationContext = new ApplicationContext();
OrderService orderService = applicationContext.getInstance(OrderService.class);
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("org.example.Dog"); // ClassNotFoundException 발생
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 일반 메서드 호출
public static void main(String[] args) {
final Dog dog = new Dog("누렁이");
dog.speak();
}
// 리플렉션을 사용했을 경우
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("org.example.Dog");
Object object = clazz.getConstructor(String.class).newInstance("누렁이");
Method method = clazz.getDeclaredMethod("speak", null);
method.setAccessible(true);
method.invoke(object, null);
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| InstantiationException
| IllegalAccessException e) {
e.printStackTrace();
}
}