Java Reflection 이 뭐지?
- JVM 기능
- 런타임 동안 클래스와 객체 정보를 추출할 수 있음
- Reflection API로 접근 가능
- JDK에 포함된 클래스 집합
Reflection이 할 수 있는 것
- 소스 코드를 수정하지 않고 새로운 프로그램 순서를 만들 수 있음.
- 실행하고 있는 클래스와 객체에 동적으로 코드 삽입.
- 실제로 사용하는 곳
- 로깅 프레임워크
- ORM
- Spring Framework
- JUnit
- Jackson
Spring Framework
@Autowired
@Configuration
@Bean
@Configuration
public class Config {
@Bean
public Connection createConnection() {
//..
}
@Bean
public Session createSession() {
//..
}
}
public class Client {
@Autowired
public Client (Connection connection, Session session) {
this.connection = connection;
this.session = session;
}
public void invoke() {
//..
}
}
- bean을 정의하고 의존성을 주입하는 과정이 reflection을 통해 이뤄짐.
Jackson library
- Reflection을 사용해 클래스를 확인하고 필드를 분석한다.
public class Listen {
private String server;
private int port;
private String upstream;
private List<String> alias;
// getter..
// setter..
}
{
"server" : "canxan",
"port" : 443,
"upstream" : "192.168.1.1"
"alias" :[
"canxan.com",
"www.canxan.com"
]
}
Listen listen = objectMapper.readValue("...", Listen.class);
String listenJson = objectMapper.writeValueAsString(listen);
Reflection 을 사용하면서 주의해야 할 점
- 런타임에서 보이지 않는 구조에 접근할 수 있음
- 목적에 맞지 않게 사용되면 코드를 변경해야 하는데 코드가 늦게 실행되거나 실행되면 위험한 상황이 발생함
- 보통 reflection에서 문제가 발생하면 어플리케이션에 심각한 문제가 생김
- “With great power, comes great responsibility”
Class<?> 오브젝트
- 객체의 런타임에 관한 정보를 포함하고 있음
- 어플리케이션의 클래스 정보
- 메서드와 필드 정보
- 상속 또는 인터페이스의 정보
Class<?> 에 접근하기
1. Object.getClass()
String stringObject = "아이유";
Singer singer = new Singer();
Map<String, Integer> map = new HashMap<>();
Class<String> stringClass = stringObject.getClass();
Class<Singer> singerClass = singer.getClass();
Class<?> mapClass = map.getClass();
- 마지막의
Map
객체는 인터페이스가 아니라 HashMap
클래스를 나타냄.
변수의 런타임 타입이기 때문임.
- Primitive 타입은 객체가 아님.
2. 타입 이름에 ‘.class’ 붙이기
- 클래스에 인스턴스가 없을 때 사용됨
- 이때 primitive 타입에 대해서 얻을 수 있음.
Class booleanType = boolean.class;
Class intType = int.class;
Class doubleType = double.class;
- 메서드 파라미터나 클래스 맴버를 확인할때 원시타입일 수 있기 때문에.
3. Class.forName()
- 정적 메서드
- 동적으로 패키지명이 포함된 클래스를 찾을 수 있음.
- Primitive 타입은 여전히 사용할 수 없음
- 잘못 입력하는 경우
ClassNotFoundException
발생
- 이때가 가장 위험함.
Class<?> stringType = Class.forName("java.lang.String");
Class<?> singerType = Class.forName("artist.Singer");
Class<?> genreType = Class.forName("artist.Singer$Genre");
package artist;
class Singer {
static class Genre {
//..
}
}
- 사용자 정의 구성 파일 (예 : bean xml) 에서 전달될때 많이 사용 됨.
<bean id="artist" class="artist.Singer">
<property name="gender" value="woman" />
</bean>
Class<?> artist = Class.forName(value);
- 외부 라이브러리인 경우 컴파일 할때 사용되기도 함.
- 컴파일 후 실행 시 해당 클래스가 어플리케이션의 클래스 경로에 추가됨.
Java wildcard
Class<?>
- Java 제네릭에서는
Integer
클래스는 Number
클래스를 상속받고, Stirng
클래스는 CharSequence
클래스를 구현하지만 List<Integer>
는 List<Number>
를 상속 받는게 아님.
- 그러나 wildcard 라고 부르는
List<?>
는 제네릭 타입이면 전부 해당하는 상위 타입임.
- 즉,
List<T>
는 List<?>
를 상속받음.
- 이는
Class<T>
와 Class<?>
관계도 마찬가지임!
Class<?>
를 사용하면 모든 파라미터 타입의 클래스 객체를 설명할 수 있다.
Class<?>
는 모든 타입의 Class<T>
에 대한 상위 타입임.
언제 쓰는건데?
컴파일 하는 동안 컴파일러가 제네릭 타입을 정확하게 알 수 없을 때
Class<?> singerType = Class.forName("artist.Singer");
클래스의 제네릭 파라미터가 제네릭 타입이라면 wildcard를 사용해야 함.
Map<String, Integer> map = new HashMap<>();
Class<Singer> singerClass = singer.getClass();
wildcard 를 사용해서 클래스가 메서드로 전달되는걸 제한할 수 있음
// Collection 을 상속받는 타입으로만 제한
public List<String> findAllMethods(Class<? extends Collection> clazz) {
//..
}
Class<?> 사용해보기
package o.e._01;
import java.util.HashMap;
import java.util.Map;
public class FirstReflection {
public static void main(String[] args) throws ClassNotFoundException {
Class<String> stringClass = String.class;
Map<String, Integer> mapObject = new HashMap<>();
Class<?> hashMapClass = mapObject.getClass();
Class<?> squareClass = Class.forName("o.e._01.FirstReflection$Square");
printClassInfo(stringClass, hashMapClass, squareClass);
}
private static void printClassInfo(Class<?> ... classes) {
for (Class<?> clazz : classes) {
System.out.println(String.format("클래스 이름 : %s, 클래스 패키지 명 : %s",
clazz.getSimpleName(),
clazz.getPackageName()));
Class<?>[] implementedInterfaces = clazz.getInterfaces();
for (Class<?> implementedInterface : implementedInterfaces) {
System.out.println(String.format("%s 클래스 implement : %s",
clazz.getSimpleName(),
implementedInterface.getSimpleName()));
}
System.out.println();
System.out.println();
}
}
private static class Square implements Drawable {
@Override
public int gerNumberOfCorners() {
return 4;
}
}
private static interface Drawable {
int gerNumberOfCorners();
}
private enum Color {
BLUE,
RED,
GREEN
}
}
> Task :FirstReflection.main()
클래스 이름 : String, 클래스 패키지 명 : java.lang
String 클래스 implement : Serializable
String 클래스 implement : Comparable
String 클래스 implement : CharSequence
String 클래스 implement : Constable
String 클래스 implement : ConstantDesc
클래스 이름 : HashMap, 클래스 패키지 명 : java.util
HashMap 클래스 implement : Map
HashMap 클래스 implement : Cloneable
HashMap 클래스 implement : Serializable
클래스 이름 : Square, 클래스 패키지 명 : o.e._01
Square 클래스 implement : Drawable
Map
의 경우 런타임에서 인터페이스가 아닌 HashMap
클래스가 됨.
var circleObject = new Drawable() {
@Override
public int gerNumberOfCorners() {
return 0;
}
};
printClassInfo(stringClass, hashMapClass, squareClass,
Collection.class, boolean.class, int[][].class, Color.class,
circleObject.getClass());
//...
private static void printClassInfo(Class<?> ... classes) {
for (Class<?> clazz : classes) {
System.out.println(String.format("클래스 이름 : %s, 클래스 패키지 명 : %s",
clazz.getSimpleName(),
clazz.getPackageName()));
Class<?>[] implementedInterfaces = clazz.getInterfaces();
for (Class<?> implementedInterface : implementedInterfaces) {
System.out.println(String.format("%s 클래스 implement : %s",
clazz.getSimpleName(),
implementedInterface.getSimpleName()));
}
System.out.println("배열 : " + clazz.isArray());
System.out.println("원시타입 : " + clazz.isPrimitive());
System.out.println("열거타입 : " + clazz.isEnum());
System.out.println("인터페이스 : " + clazz.isInterface());
System.out.println("익명클래스 : " + clazz.isAnonymousClass());
System.out.println();
System.out.println();
}
}
클래스 이름 : Collection, 클래스 패키지 명 : java.util
Collection 클래스 implement : Iterable
배열 : false
원시타입 : false
열거타입 : false
인터페이스 : true
익명클래스 : false
클래스 이름 : boolean, 클래스 패키지 명 : java.lang
배열 : false
원시타입 : true
열거타입 : false
인터페이스 : false
익명클래스 : false
클래스 이름 : int[][], 클래스 패키지 명 : java.lang
int[][] 클래스 implement : Cloneable
int[][] 클래스 implement : Serializable
배열 : true
원시타입 : false
열거타입 : false
인터페이스 : false
익명클래스 : false
클래스 이름 : Color, 클래스 패키지 명 : o.e._01
배열 : false
원시타입 : false
열거타입 : true
인터페이스 : false
익명클래스 : false
클래스 이름 : , 클래스 패키지 명 : o.e._01
클래스 implement : Drawable
배열 : false
원시타입 : false
열거타입 : false
인터페이스 : false
익명클래스 : true