저번 포스팅까지 자바의 스트림에 대한 내용을 알아보았다. 이제 자바에 대해 평소에 많이 다루지 않은 고급(?) 내용들에 대해 언급하려고 한다. 이번에는 자바의 리플렉션에 대해 살펴볼 것이다.
리플렉션이란 무엇인가. 자바에서 리플렉션은 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API이다. 동적으로 클래스를 사용할 때 필요하며 평소에 우리가 리플렉션을 활용하는 예시로는 자바 스프링 프레임워크를 사용할 때 롬복(lombok)과 같은 플러그인을 사용하여 어노테이션을 클래스에 붙이는 기능이 있다. 이 또한 자바의 리플렉션 기능을 이용한다고 할 수 있다.
리플렉션은 java.lang.reflect 패키지에 있는 클래스와 인터페이스를 사용하여 구현된다. 이 패키지는 클래스의 필드, 메서드, 생성자 등과 같은 구성 요소에 접근할 수 있는 메커니즘을 제공하며, 주요한 클래스는 다음과 같다.
Class: 자바 클래스와 인터페이스에 대한 메타데이터를 표현하는데 사용된다. 예를 들어, 클래스의 이름, 슈퍼클래스, 인터페이스, 필드, 메서드 등의 정보를 얻을 수 있다.
Field: 클래스의 필드에 대한 메타데이터를 표현한다. 이를 통해 필드의 이름, 유형, 접근 제어자 등을 알 수 있다.
Method: 클래스의 메서드에 대한 메타데이터를 표현한다. 이를 통해 메서드의 이름, 매개변수 유형, 반환 유형, 접근 제어자 등을 알 수 있다.
Constructor: 클래스의 생성자에 대한 메타데이터를 표현한다. 이를 통해 생성자의 매개변수 유형, 접근 제어자 등을 알 수 있다.
위와 같은 상황에서 리플렉션은 많이 사용된다. 그러나 이러한 리플렉션이 가지고 있는 단점도 존재한다.
따라서 리플렉션을 사용할 때는 성능과 보안 측면을 고려하여 신중하게 사용해야 한다.
이제 하나씩 예제와 함께 어떻게 리플렉션이 쓰이는지 알아보자.
리플렉션에서 Class 클래스는 자바 클래스에 대한 메타데이터를 제공한다. 이 클래스를 사용하여 클래스의 이름, 패키지, 슈퍼클래스, 인터페이스, 필드, 메서드, 생성자 등에 대한 정보를 얻을 수 있다. 아래에 예제를 통해 Class 클래스를 사용하는 방법을 자세히 살펴보자.
import java.lang.reflect.*;
public class ReflectionExample {
public static void main(String[] args) {
// 클래스 이름을 사용하여 Class 객체 얻기
Class<?> clazz = String.class;
// 클래스의 이름 출력
System.out.println("Class Name: " + clazz.getName());
// 클래스의 패키지 출력
Package pkg = clazz.getPackage();
System.out.println("Package: " + pkg.getName());
// 클래스의 슈퍼클래스 출력
Class<?> superClass = clazz.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
// 클래스의 인터페이스 출력
Class<?>[] interfaces = clazz.getInterfaces();
System.out.print("Interfaces: ");
for (Class<?> iface : interfaces) {
System.out.print(iface.getName() + ", ");
}
System.out.println();
// 클래스의 생성자 출력
Constructor<?>[] constructors = clazz.getConstructors();
System.out.println("Constructors:");
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// 클래스의 필드 출력
Field[] fields = clazz.getFields();
System.out.println("Fields:");
for (Field field : fields) {
System.out.println(field);
}
// 클래스의 메서드 출력
Method[] methods = clazz.getMethods();
System.out.println("Methods:");
for (Method method : methods) {
System.out.println(method);
}
}
}
위의 예제에서는 String 클래스를 대상으로 Class 객체를 얻어와서 다양한 메타데이터를 출력하는 방법을 보여준다. 아래 메서드를 살펴보자.
getName(): 클래스의 이름을 반환한다.getPackage(): 클래스의 패키지를 반환한다.getSuperclass(): 클래스의 슈퍼클래스를 반환한다.getInterfaces(): 클래스가 구현한 인터페이스들을 반환한다.getConstructors(): 클래스의 생성자들을 반환한다.getFields(): 클래스의 공개(public) 필드들을 반환한다.getMethods(): 클래스의 공개(public) 메서드들을 반환한다.위의 메서드들은 Class 객체를 통해 해당 클래스의 메타데이터를 동적으로 조사할 수 있게 해준다. 이러한 기능을 통해 리플렉션을 사용하여 클래스의 구조를 분석하고 필요한 정보를 동적으로 활용할 수 있다.
리플렉션에서 Field 클래스는 클래스의 필드에 대한 메타데이터를 나타낸다. 이 클래스를 사용하면 실행 중에 필드의 이름, 유형, 접근 제어자 등을 조사하고 필드의 값을 읽거나 수정할 수 있다.
자바에서 필드는 클래스의 속성을 나타내며, 클래스의 인스턴스마다 고유한 값을 가질 수 있다. Field 클래스를 사용하여 필드에 대한 메타데이터를 얻으면 다음과 같은 작업을 수행할 수 있다.
필드의 이름 반환: getName() 메서드를 사용하여 필드의 이름을 반환할 수 있다.
필드의 유형 반환: getType() 메서드를 사용하여 필드의 유형을 얻을 수 있다. 이를 통해 필드가 어떤 데이터 유형을 저장하는지 확인할 수 있다.
필드의 접근 제어자 얻기: getModifiers() 메서드를 사용하여 필드의 접근 제어자를 얻을 수 있다. 이를 통해 필드가 public, private, protected 등의 접근 제어자를 가지고 있는지 확인할 수 있다.
필드의 값 읽기: get(Object obj) 메서드를 사용하여 특정 객체에서 해당 필드의 값을 읽을 수 있다. 이 때, obj 매개변수는 필드를 소유한 객체를 의미한다.
필드의 값 수정하기: set(Object obj, Object value) 메서드를 사용하여 특정 객체에 해당 필드의 값을 설정할 수 있다. 이 때, obj 매개변수는 필드를 소유한 객체이고, value 매개변수는 필드에 설정할 값을 의미한다.
리플렉션을 사용하여 필드에 접근할 때는 보안 규칙을 우회하기 위해 setAccessible(true)를 호출해야 할 수 있다. 이는 private 필드에 접근하기 위한 조치이다. 그러나 주의할 점은 당연한 이야기이지만 이러한 조치가 보안에 취약할 수 있으므로 꼭 필요한 경우에만 사용해야 한다.
위의 설명을 토대로 다음 예제에서는 Field 클래스를 사용하여 클래스의 필드를 동적으로 조사하고 값을 읽고 수정하는 방법을 살펴보자.
import java.lang.reflect.Field;
public class FieldNameExample {
public static void main(String[] args) throws NoSuchFieldException {
Field field = MyClass.class.getDeclaredField("myField");
String fieldName = field.getName();
System.out.println("Field Name: " + fieldName);
}
}
class MyClass {
private String myField;
}
import java.lang.reflect.Field;
public class FieldTypeExample {
public static void main(String[] args) throws NoSuchFieldException {
Field field = MyClass.class.getDeclaredField("myField");
Class<?> fieldType = field.getType();
System.out.println("Field Type: " + fieldType.getName());
}
}
class MyClass {
private String myField;
}
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldModifiersExample {
public static void main(String[] args) throws NoSuchFieldException {
Field field = MyClass.class.getDeclaredField("myField");
int modifiers = field.getModifiers();
String modifierString = Modifier.toString(modifiers);
System.out.println("Modifiers: " + modifierString);
}
}
class MyClass {
private String myField;
}
import java.lang.reflect.Field;
public class FieldGetValueExample {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
MyClass obj = new MyClass();
Field field = obj.getClass().getDeclaredField("myField");
field.setAccessible(true); // private 필드에 접근하기 위해
Object value = field.get(obj);
System.out.println("Field Value: " + value);
}
}
class MyClass {
private String myField = "Hello, Reflection!";
}
import java.lang.reflect.Field;
public class FieldSetValueExample {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
MyClass obj = new MyClass();
Field field = obj.getClass().getDeclaredField("myField");
field.setAccessible(true); // private 필드에 접근하기 위해
field.set(obj, "Modified Value");
System.out.println("Modified Field Value: " + obj.getMyField());
}
}
class MyClass {
private String myField = "Original Value";
public String getMyField() {
return myField;
}
}
위의 각 예제에서는 필드의 이름, 유형, 접근 제어자, 값 읽기, 값 수정하기에 대한 작업을 수행하는 방법을 보여준다. 코드의 내용은 어렵지 않으니 상세한 설명은 생략한다.
메서드(Method)는 클래스의 메서드에 대한 메타데이터를 나타낸다. 이를 통해 실행 중인 클래스의 메서드를 동적으로 확인하고 호출할 수 있다. 메서드의 이름, 매개변수 유형, 반환 유형, 접근 제어자 등의 정보를 얻을 수 있다. 또한 메서드를 호출하여 해당 메서드를 실행할 수도 있다.
리플렉션에서 메서드를 다루는 데 사용되는 주요 클래스는 java.lang.reflect.Method이다. 이 클래스를 사용하여 메서드에 대한 정보를 얻고 호출할 수 있다.
메서드와 관련된 주요 요소는 다음과 같다.
메서드 정보 얻기: 클래스의 메서드에 대한 메타데이터를 얻을 수 있다. 메서드의 이름, 매개변수 유형, 반환 유형, 접근 제어자 등을 확인할 수 있다.
메서드 호출하기: 얻어진 메서드 객체를 사용하여 해당 메서드를 호출할 수 있다. 이를 통해 실행 중인 클래스의 메서드를 동적으로 호출할 수 있다.
메서드의 매개변수 정보 얻기: 메서드에 선언된 매개변수에 대한 정보를 얻을 수 있다. 매개변수의 이름, 유형 등을 확인할 수 있다.
메서드의 반환값 처리하기: 메서드 호출 결과로 반환된 값을 처리할 수 있다.
일반적인 메서드 호출보다 리플렉션을 사용한 메서드 호출이 느릴 수 있으며, 잘못된 사용은 보안 위험을 초래할 수 있다. 따라서 항상 주의해야하는 사항이지만 다시 한번 언급하자면, 리플렉션을 사용할 때는 꼭 성능과 보안을 고려하여 신중하게 사용해야 한다.
이제 각각의 예제를 살펴보자.
import java.lang.reflect.Method;
public class MethodInfoExample {
public static void main(String[] args) throws NoSuchMethodException {
Method method = MyClass.class.getDeclaredMethod("myMethod", int.class, String.class);
// 메서드의 이름 출력
System.out.println("Method Name: " + method.getName());
// 메서드의 매개변수 유형 출력
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.print("Parameter Types: ");
for (Class<?> paramType : parameterTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
// 메서드의 반환 유형 출력
Class<?> returnType = method.getReturnType();
System.out.println("Return Type: " + returnType.getName());
// 메서드의 접근 제어자 출력
int modifiers = method.getModifiers();
System.out.println("Modifiers: " + Modifier.toString(modifiers));
}
}
class MyClass {
public void myMethod(int num, String str) {
// 메서드 내용
}
}
이 예제에서는 MyClass 클래스의 myMethod 메서드에 대한 메타데이터를 얻는 방법을 보여준다. getDeclaredMethod() 메서드를 사용하여 메서드의 이름과 매개변수 유형을 지정하여 Method 객체를 얻는다. 그런 다음 해당 메서드의 이름, 매개변수 유형, 반환 유형, 접근 제어자를 출력한다.
import java.lang.reflect.Method;
public class MethodInvokeExample {
public static void main(String[] args) throws Exception {
MyClass obj = new MyClass();
Method method = MyClass.class.getDeclaredMethod("myMethod", int.class, String.class);
// 메서드 호출
method.invoke(obj, 10, "Hello");
}
}
class MyClass {
public void myMethod(int num, String str) {
System.out.println("Number: " + num + ", String: " + str);
}
}
이 예제에서는 MyClass 클래스의 myMethod 메서드를 호출하는 방법을 보여준다. getDeclaredMethod() 메서드를 사용하여 호출할 메서드를 지정하여 Method 객체를 얻은 후, invoke() 메서드를 사용하여 해당 메서드를 호출한다. 이때, 첫 번째 매개변수로는 메서드를 호출할 객체를 전달하고, 나머지 매개변수로는 메서드에 전달할 인수를 전달한다.
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class MethodParameterInfoExample {
public static void main(String[] args) throws NoSuchMethodException {
Method method = MyClass.class.getDeclaredMethod("myMethod", int.class, String.class);
// 메서드의 매개변수 정보 출력
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println("Parameter Name: " + parameter.getName());
System.out.println("Parameter Type: " + parameter.getType().getName());
}
}
}
class MyClass {
public void myMethod(int num, String str) {
// 메서드 내용
}
}
이 예제에서는 MyClass 클래스의 myMethod 메서드의 매개변수 정보를 얻는 방법을 보여준다. getParameters() 메서드를 사용하여 메서드의 매개변수에 대한 Parameter 배열을 얻은 후, 각 매개변수의 이름과 유형을 출력한다.
import java.lang.reflect.Method;
public class MethodReturnValueExample {
public static void main(String[] args) throws Exception {
MyClass obj = new MyClass();
Method method = MyClass.class.getDeclaredMethod("myMethod", int.class, String.class);
// 메서드 호출 및 반환값 처리
Object returnValue = method.invoke(obj, 10, "Hello");
System.out.println("Return Value: " + returnValue);
}
}
class MyClass {
public String myMethod(int num, String str) {
return "Number: " + num + ", String: " + str;
}
}
이 예제에서는 MyClass 클래스의 myMethod 메서드를 호출하고 반환값을 처리하는 방법을 보여준다. invoke() 메서드를 사용하여 메서드를 호출하고, 호출 결과로 반환된 값을 변수에 저장한 후 출력한다.
이제 마지막으로 생성자에 대한 설명과 함께 글을 마무리 하자.
리플렉션의 생성자(Constructor)는 클래스의 생성자에 대한 메타데이터를 나타낸다. 리플렉션을 사용하면 실행 중에 클래스의 생성자에 접근하고 생성자를 호출할 수 있다. 또한, 생성자의 매개변수 유형, 접근 제어자 등의 정보를 얻을 수 있다.
리플렉션에서 생성자를 다루는 데 사용되는 주요 클래스는 java.lang.reflect.Constructor이다. 이 클래스를 사용하여 생성자에 대한 정보를 얻고 인스턴스를 생성할 수 있다.
생성자와 관련된 주요 작업은 다음과 같다.
생성자 정보 반환: 클래스의 생성자에 대한 메타데이터를 반환한다. 이를 통해 생성자의 매개변수 유형, 접근 제어자 등을 확인할 수 있다.
인스턴스 생성하기: 얻어진 생성자 객체를 사용하여 클래스의 인스턴스를 생성할 수 있다.
이제 각각 예제를 살펴보자.
import java.lang.reflect.Constructor;
public class ConstructorInfoExample {
public static void main(String[] args) throws NoSuchMethodException {
Constructor<?> constructor = MyClass.class.getDeclaredConstructor(int.class, String.class);
// 생성자의 매개변수 유형 출력
Class<?>[] parameterTypes = constructor.getParameterTypes();
System.out.print("Parameter Types: ");
for (Class<?> paramType : parameterTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
// 생성자의 접근 제어자 출력
int modifiers = constructor.getModifiers();
System.out.println("Modifiers: " + Modifier.toString(modifiers));
}
}
class MyClass {
public MyClass(int num, String str) {
// 생성자 내용
}
}
이 예제에서는 MyClass 클래스의 생성자에 대한 메타데이터를 얻는 방법을 보여준다. getDeclaredConstructor() 메서드를 사용하여 생성자의 매개변수 유형을 지정하여 Constructor 객체를 얻은 후, 해당 생성자의 매개변수 유형과 접근 제어자를 출력한다.
import java.lang.reflect.Constructor;
public class CreateInstanceExample {
public static void main(String[] args) throws Exception {
Constructor<MyClass> constructor = MyClass.class.getDeclaredConstructor(int.class, String.class);
// 인스턴스 생성
MyClass obj = constructor.newInstance(10, "Hello");
System.out.println("Instance Created: " + obj);
}
}
class MyClass {
public MyClass(int num, String str) {
// 생성자 내용
}
}
이 예제에서는 MyClass 클래스의 생성자를 사용하여 클래스의 인스턴스를 생성하는 방법을 보여준다. getDeclaredConstructor() 메서드를 사용하여 생성자의 매개변수 유형을 지정하여 Constructor 객체를 얻은 후, newInstance() 메서드를 사용하여 해당 생성자를 호출하여 인스턴스를 생성한다.
이렇게해서 자바의 리플렉션에 대해 알아보았다. 처음 들었을 때 꽤 생소한 단어라 어색하고 어떠한 개념인지 이해하기가 조금 어려웠다. 그러나 예제와 함께 클래스, 필드, 메서드, 생성자와 같이 익숙한 단어를 접하니 개념적으로 이해도 쉽게 되었고 평소 스프링에서 항상 써왔다는 것을 알게 되었다. 다음 포스팅으로는 중간에 언급했었던 자바의 어노테이션을 스프링과 접해서 간단하게 설명해 볼 예정이다.