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