리플렉션 ( Reflection ) 이란
구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드,타입,변수들에
접근할 수 있도록 해주는 자바 API
컴파일 단계가 아닌 런타임 단계에서 동적으로 특정 클래스의 정보를
추출할 수 있는 프로그래밍 기법
자바 Class 클래스 ( Java.lang.Class )
코드를 실행하기 전 컴파일 단에서 개발자가 직접 클래스를 선언하는것이 아닌. 코드 상에서 호출 로직을 통해 클래스 정보를 얻어와 다룸으로써 런타임 단에서 다이나믹하게 클래스를 핸들링하기 위해 사용하는 객체.
Class 클래스는 java.lang.Class 패키지에 별도로 존재하는 독립형 클래스로서, 자신이 속한 클래스의 모든 멤버 정보를 담고 있기 때문에 런타임 환경에서 동적으로 저장된 클래스나 인터페이스 정보를 가져오는데 사용된다.
( 오해하지 말아야 될 것이 클래스 자료형이 아닌 클래스의 명이 " Class " 인 클래스이다 )
자바의 모든 클래스와 인터페이스는 컴파일 후 .java => .class 파일로 변환된다. 이 .class 파일에는 멤버 변수.메서드,생성자 등 객체의 정보들이 있는데, JVM의 클래스 로더 ( Class Loader ) 에 의해서 클래스 파일이 메모리에 올라갈 때, Class 클래스는 이 .class 파일의 클래스 정보들을 가져와 힙영역에 자동으로 객체화가 되게 된다.
그래서 따로 new 인스턴스화 없이 바로 가져와 사용하면 된다.
JVM 의 클래스 로더 ( Class Loader ) 는 실행시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.
먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고 있으면 객체의 참조를 반환하고,
없으면 ClassPath에 지정된 경로를 따라서 클래스 파일을 찾아 해당 클래스 파일을 읽어서 Class 객체로 변환한다.
만일 찾지 못한다면 ClassNotFoundException이 발생하게 되는것.
리플렉션의 장단점
장점
- 유연성과 확장성
- 구체적인 클래스를 알지 못해도 동적으로 클래스를 만들어서 의존 관계를 맺어줄 수 있다.
- 런타임 시점에서 클래스의 인스턴스를 생성하고, 접근 제어자와 관계없이 필드와 메소드에 접근하여 필요한 작업을 수행할 수 있는 유연성을 가지고 있다.
- 개발 규모가 큰 스프링인 경우, 리플렉션을 이용한 Dynamic Proxy를 통해 @Autowired,@Service,@Controller,@Repository와 같은 DI 어노테이션을 활용한다
단점
- 캡슐화를 저해한다
- 런타임 시점에서 인스턴스를 생성하므로 컴파일 시점에서 해당 타입을 체크할 수 없다.
- 런타임 시점에서 인스턴스를 생성하므로 구체적인 동작의 흐름을 파악하기가 어렵다.
- 단순히 필드 및 메소드를 접근할 때보다 리플렉션을 사용하여 접근할 때 성능이 느리다
( 모든 상황에서 느리지는 않음 )
Class 클래스 객체 얻기
자바에서 Class 객체를 가져오는 방법은 총 3가지가 존재한다.
다음은 우리가 익히 아는 String 클래스 객체를 생성하고, 세가지 방법으로 String 클래스의 Class 클래스 정보를 얻는 예시이다
Object.getClass()로 얻기
- 모든 클래스의 최상위 클래스인 Object 클래스에서 제공하는 getClass() 메서드를 통해 가져온다.
- 단 , 해당 클래스가 인스턴스화 된 상태 이어야 한다는 제약이 있다.
public static void main(String[] args) {
// String 클래스 인스턴스화
String str = new String("Class 클래스");
//getClass() 메서드로 얻기
Class<? extends String> strCls = str.getClass();
System.out.println(strCls); // class.java.lang.String
}
.class 리터럴로 얻기
- 인스턴스가 존재하지 않고, 컴파일된 클래스 파일만 있다면 리터럴로 Class 객체를 곧바로 얻을 수 있다.
- 가장 심플하게 Class객체를 가져오는 방법
public static void main(String[] args) {
//클래스 리터럴 (*.class)로 얻기
Class<? extends String> strCls = String.class;
System.out.println(strCls); // class.java.lang.String
}
Class.forName() 으로 얻기
- 위의 리터럴 방식과 같이 컴파일된 클래스 파일이 있다면 클래스 이름만으로 Class 객체를 반환 받을 수 있다.
- 이때는 클래스의 도메인을 상세히 적어주어야 한다. 그래서 클래스 파일 경로에 오타가 있으면
에러가 발생할 수 있기 때문에 주의해야 한다. - 만일 Class 객체를 찾지 못한다면 ClassNotFoundException를 발생시키기 때문에 예외처리가 강제된다.
- 그러나 다른 두가지 방법보다 forName을 통해 얻게되면 메모리를 절약하며 동적 로딩 할 수 있기 때문에 가장 성능이 높다.
public static void main (String[] args){
try{
// 도메인.클래스명으로 얻기
Class<?> strCls = Class.forName("java.lang.String");
System.out.println(strCls); // class java.lang.String
}catch (ClassNotFoundException e){}
}
forName() 메서드를 통해 가져오는 방법을 " 동적 로딩 " 이라고 부른다. 보통 다른 클래스 파일을 불러올때는 컴파일 시 JVM의 Method Area에 클래스 파일이 같이 바인딩( Binding )이 되지만, forName()으로 .class파일을 불러올 때는 컴파일에 바인딩이 되지 않고 런타임때 불러오게 되기 때문에 동적 로딩이라고 부른다.
그래서 컴파일 타임에 체크할 수 없기 때문에 클래스 유무가 확인되지 않아 예외처리를 해주어야 하는 이유이기도 하다.
'JAVA' 카테고리의 다른 글
JAVA - 클래스의 초기화 & 로드 (1) | 2024.04.12 |
---|---|
JAVA - Optional 사용하기 (0) | 2023.03.30 |
RestAPI 정리 (0) | 2023.03.06 |
빌더 패턴이란?? (0) | 2023.02.20 |
JAVA 9 - Collection Factory Method (0) | 2023.02.20 |