: 힙 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API
클래스.class
로 가져오기인스턴스.getClass()
로 가져오기Class.forName("클래스명")
으로 가져오기예시
public class Member {
private String name;
protected int age;
public String hobby;
public Member() {
}
public Member(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public void speak(String message) {
System.out.println(message);
}
private void secret() {
System.out.println("비밀번호는 1234입니다.");
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<Member> memberClass = Member.class;
System.out.println(System.identityHashCode(memberClass));
Member member = new Member("제이온", 23, "다라쓰 개발");
Class<? extends Member> memberClass2 = member.getClass();
System.out.println(System.identityHashCode(memberClass2));
Class<?> memberClass3 = Class.forName("{패키지명}.Member");
System.out.println(System.identityHashCode(memberClass3));
}
}
// 실행 결과
1740000325
1740000325
1740000325
해당 클래스의 인스턴스를 생성
public class Main {
public static void main(String[] args) throws Exception {
// Member의 모든 생성자 출력
Member member = new Member();
Class<? extends Member> memberClass = member.getClass();
Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
// Member의 기본 생성자를 통한 인스턴스 생성
Constructor<? extends Member> constructor = memberClass.getConstructor();
Member member2 = constructor.newInstance();
System.out.println("member2 = " + member2);
// Member의 다른 생성자를 통한 인스턴스 생성
Constructor<? extends Member> fullConstructor =
memberClass.getConstructor(String.class, int.class, String.class);
Member member3 = fullConstructor.newInstance("제이온", 23, "다라쓰 개발");
System.out.println("member3 = " + member3);
}
}
// 실행 결과
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
member3 = Member{name='제이온', age=23, hobby='다라쓰 개발'}
getConstructor() 를 통해 생성자를 얻어 오고, new Instance()를 통해 Member 인스턴스를 동적으로 생성 해 줄수 있다.
인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 접근하여 사용
public class Main {
public static void main(String[] args) throws Exception {
Member member = new Member("제이온", 23, "다라쓰 개발");
Class<? extends Member> memberClass = member.getClass();
// 필드 접근
Field[] fields = memberClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(member));
}
fields[0].set(member, "제이온2");
System.out.println(member);
// 메소드 접근
Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
speakMethod.invoke(member, "리플렉션 테스트");
Method secretMethod = memberClass.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(member);
}
}
// 실행 결과
제이온
23
다라쓰 개발
Member{name='제이온2', age=23, hobby='다라쓰 개발'}
리플렉션 테스트
비밀번호는 1234입니다.
*이때 주의할 점은 private 접근 제어자가 있는 필드에 접근할 때는 setAccessible()의 인자를 true로 넘겨 주어야 한다.
*private 접근 제어자가 있는 메소드에 접근 할 때는 setAccessible() 의 인자를 true로 설정해야 한다.
런타임 시점에서 클래스의 인스턴스를 생성하고, 접근 제어자와 관계 없이 필드와 메소드에 접근하여 필요한 작업을 수행 할 수 있는 유연성을 가지고 있다.
Performance의 오버헤드 : Reflection에는 동적으로 해석되는 유형이 포함되므로, 특정 JVM 최적화를 수행할 수 없다. 따라서 Reflection 작업이 비 Reflection 작업보다 성능이 떨어지며, 성능에 민감한 애플리케이션에서 자주 호출되는 코드엔 사용하지 않아야 한다.
보안 제안 사항 : Reflection에는 시큐리티 매니저의 실행 시에 존재하지 않는 실행 시 액세스 권한이 필요하다. 이것은 애플릿과 같이 제한된 보안 컨텍스트에서 실행되어야 하는 코드에 대한 중요한 고려 사항이다
리플렉션 API를 통해 런타임 중, 클래스 정보에 접근하여 클래스를 원하는대로 조작 할 수 있다.
규모가 작은 콘솔 단계에서는 개발자가 충분히 컴파일 시점에 프로그램에서 사용될 객체와 의존 관계를 모두 파악할 수 있다. 하지만 프레임워크와 같이 큰 규모의 개발 단계에서는 수많은 객체와 의존 관계를 파악하기 어렵다. 이때 리플렉션을 사용하면 동적으로 클래스를 만들어서 의존 관계를 맺어줄 수 있다
Spring의 Bean Factory를 보면, @Controller @Service 등 어노테이션만 붙이면 알아서 해당 어노테이션이 붙은 클래스를 생성하고 관리해주는 것을 알 수 있는데. 이것이 가능한 이유는 바로 리플렉션이다. 런타임에 어노테이션이 붙은 클래스를 탐색하고 발견한다면, 리플렉션을 통해 해당 클래스의 인스턴스를 생성하고 필요한 필드를 주입하여 Bean Factory에 저장하는 식으로 사용된다