🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
이 포스팅에서는 리플렉션이 무엇이고 어떤 장/단점이 있는지와 리플렉션을 사용하는 간단한 코드를 볼 것이고, 왜 DB에서 객체를 생성할 때와 Jackson 역직렬화할 때 리플렉션의 기본 생성자를 사용하는지를 알아보려고 한다.
oracle 자바 공식문서에는 다음과 같이 적혀있다.
Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.
해석을 하자면 다음과 같다.
즉, 리플렉션은 Java의 고급 기능 중 하나로, 프로그램이 런타임 중에 클래스, 메서드, 필드 등과 같은 코드의 구조를 동적으로 검사하고 수정할 수 있게 해준다. 리플렉션을 사용하면 컴파일 시점에 알 수 없는 정보에 접근하거나, 일반적인 코드 흐름에서 다루지 않는 작업(ex. private)을 수행할 수 있다.
아래 또한, oracle 자바 공식문서에 있는 내용이다.
확장성: 애플리케이션이 외부 사용자 정의 클래스를 런타임에 인스턴스화할 수 있다.
(정의되어 있지 않은 외부의 클래스를 런타임 시 동적으로 객체 생성 가능)
클래스 브라우저 및 개발 환경: 클래스 멤버를 열거하고 타입 정보를 제공하여 개발자가 코드를 작성하는 데 도움을 준다.
(클래스의 메타 데이터를 제공. ex. 메서드, 필드, 생성자)
디버거 및 테스트 도구: 비공개 멤버를 검사하고 클래스의 메서드를 자동으로 호출하여 테스트 커버리지를 높일 수 있음.
성능 저하: 리플렉션은 동적 타입 확인으로 인해 JVM 최적화가 어렵고, 비반사적 코드보다 성능이 느림.
보안 제한: 보안 관리자가 활성화된 경우 리플렉션은 실행 권한이 필요하여 동작하지 않을 수 있음.
(보안 관리자가 무엇인지는 아래 설명을 보면 됩니다.)
내부 노출: 비공개 필드와 메서드에 접근할 수 있어 예기치 않은 부작용을 초래할 수 있으며, 코드의 추상화를 깨뜨려 호환성 문제를 일으킬 수 있음.
보안 관리자란,
Java 애플리케이션의 보안 정책을 강제하는 JVM의 기능이다.
설정 방법은 아래와 같다.
JVM 옵션에
java -Djava.security.manager -Djava.security.policy=path/to/your.policy YourMainClass를 설정.
-Djava.security.manager옵션은 JVM이 보안 관리자를 활성화하도록 설정하는 것.
-Djava.security.policy는 보안 정책 파일의 경로를 지정하여 애플리케이션의 권한을 설정하는 것.
Object myClass = new MyClass();
Class<?> classInfo = myClass.getClass(); // 객체의 런타임 클래스 정보 가져오기
// 클래스의 단순 이름을 반환. "MyClass"
System.out.println(classInfo.getSimpleName());
// 클래스의 전체 이름(패키지를 포함한 이름)을 반환
System.out.println(classInfo.getName());
System.out.println(classInfo.getCanonicalName());
// 클래스의 패키지 이름 반환
Package pkgInfo = classInfo.getPackage();
System.out.println(pkgInfo.getName());
Object myClass = new MyClass();
Class<?> classInfo = myClass.getClass();
// 부모 클래스 불러오기. 상속중인 부모 클래스가 없다면 최상위 클래스인 Object
Class<?> superClassInfo = classInfo.getSuperclass();
System.out.println(superClassInfo.getSimpleName());
// 해당 클래스가 구현한 인터페이스를 반환
Class<?>[] interfaces = classInfo.getInterfaces();
for (Class<?> iface : interfaces) {
System.out.println(iface.getSimpleName());
}
Class<?> aclass = Class.forName("com.MyClass"); // 지정된 클래스 이름을 사용하여 해당 클래스의 Class 객체를 가져옴
// 클래스에 선언된 모든 생성자 불러오기
// getConstructors()는 public 생성자만.
// 모든 권한에 대한 생성자는 getDeclaredConstructors() 사용.
Constructor<?>[] constructors = aclass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// 특정 생성자를 통한 인스턴스 생성
// 위와 똑같이 모든 권한에 대한 생성자는 getDeclaredConstructor()
Constructor<?> constructor = aclass.getConstructor(int.class, String.class);
MyClass myClassInstance = (MyClass) constructor.newInstance(123, "Hi");
// 기본 생성자
MyClass abc = (MyClass) aclass.getDeclaredConstructor().newInstance();
Class<?> aclass = Class.forName("com.MyClass");
// 해당 클래스와 부모 클래스, 인터페이스에서 정의된 public 메서드들을 반환
Method[] methods = aclass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// 해당 클래스에 선언된 모든 메서드(비공개 포함)를 반환
Method[] declaredMethods = aclass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
Class<?> aclass = Class.forName("com.MyClass");
// 해당 클래스 및 부모 클래스의 public 필드를 반환
Field[] fields = aclass.getFields();
for (Field field : fields) {
System.out.println(field);
}
// 해당 클래스에 선언된 모든 필드(비공개 포함)를 반환
Field[] declaredFields = aclass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
Class<?> aclass = Class.forName("com.MyClass");
MyClass myclass = (MyClass) aclass.getDeclaredConstructor().newInstance();
// 특정 필드 값 접근
// getField("x")는 해당 클래스 및 부모 클래스의 public "x"필드를 반환
Field field = aclass.getDeclaredField("x");
// 필드 타입 반환
Class<?> fieldType = field.getType();
System.out.println(fieldType.getName());
// 비공개 필드에 접근 가능하도록 설정
field.setAccessible(true);
// 필드 값 읽기
Object value = field.get(myclass);
// 필드 값 설정
field.set(myClassInstance, 10);