웹사이트 검색

자바 리플렉션 예제 튜토리얼


Java Reflection은 애플리케이션의 런타임 동작을 검사하고 수정하는 기능을 제공합니다. Java의 Reflection은 핵심 Java의 고급 주제 중 하나입니다. 자바 리플렉션을 사용하면 컴파일 시간에 클래스에 액세스할 수 없더라도 런타임에 클래스, 열거형을 검사하고 해당 구조, 메서드 및 필드 정보를 얻을 수 있습니다. 또한 리플렉션을 사용하여 개체를 인스턴스화하고 메서드를 호출하고 필드 값을 변경할 수 있습니다.

자바 리플렉션

  1. 자바의 리플렉션
  2. 클래스용 Java 리플렉션\n
    • 클래스 개체 가져오기
    • 슈퍼 클래스 받기
    • 공용 회원 클래스 가져오기
    • 선언된 클래스 가져오기
    • 선언 클래스 가져오기
    • 패키지 이름 얻기
    • 클래스 수정자 가져오기
    • 유형 매개변수 가져오기
    • 구현된 인터페이스 가져오기
    • 모든 공용 메소드 가져오기
    • 모든 공용 생성자 가져오기
    • 모든 공용 필드 가져오기
    • 모든 주석 가져오기
  3. 필드용 Java 리플렉션\n
    • 공개 필드 가져오기
    • 필드 선언 클래스
    • 필드 유형 가져오기
    • 공용 필드 값 가져오기/설정
    • 비공개 필드 값 가져오기/설정
  4. 메서드에 대한 Java 리플렉션\n
    • 공개 메소드 가져오기
    • 공개 메서드 호출
    • 개인 메서드 호출
  5. 생성자를 위한 Java 리플렉션\n
    • 공개 생성자 가져오기
    • 생성자를 사용하여 개체 인스턴스화
  6. 주석에 대한 Java 리플렉션

자바에서의 리플렉션

Java의 리플렉션은 매우 강력한 개념이며 일반 프로그래밍에서는 거의 사용되지 않지만 대부분의 Java, J2EE 프레임워크의 근간입니다. Java 리플렉션을 사용하는 일부 프레임워크는 다음과 같습니다.

  1. JUnit - 리플렉션을 사용하여 @Test 주석을 구문 분석하여 테스트 메서드를 가져온 다음 호출합니다.
  2. Spring - 종속성 주입, Spring 종속성 주입에서 자세히 알아보기
  3. Tomcat 웹 컨테이너는 web.xml 파일과 요청 URI를 구문 분석하여 올바른 모듈로 요청을 전달합니다.
  4. Eclipse 메소드 이름 자동 완성
  5. 스트럿
  6. 최대 절전

목록은 끝이 없으며 모두 Java 리플렉션을 사용합니다. 이러한 모든 프레임워크는 사용자 정의 클래스, 인터페이스, 메서드 등에 대한 지식과 액세스 권한이 없기 때문입니다. 다음과 같은 단점.

  • 성능 저하 - Java 리플렉션은 유형을 동적으로 해결하기 때문에 로드할 클래스를 찾기 위해 클래스 경로를 스캔하는 것과 같은 처리가 수반되어 성능이 저하됩니다.
  • 보안 제한 - 리플렉션에는 보안 관리자에서 실행 중인 시스템에 사용할 수 없는 런타임 권한이 필요합니다. 이로 인해 보안 관리자로 인해 런타임 시 애플리케이션이 실패할 수 있습니다.
  • 보안 문제 - 리플렉션을 사용하면 액세스해서는 안 되는 코드 부분에 액세스할 수 있습니다. 예를 들어 클래스의 비공개 필드에 액세스하고 값을 변경할 수 있습니다. 이는 심각한 보안 위협이 될 수 있으며 애플리케이션이 비정상적으로 작동하게 할 수 있습니다.
  • 높은 유지 관리 - 리플렉션 코드는 이해 및 디버깅이 어렵고, 클래스를 사용할 수 없기 때문에 컴파일 시간에 코드 관련 문제를 찾을 수 없어 유연성이 떨어지고 유지 관리가 어렵습니다.

클래스에 대한 Java 리플렉션

Java에서 모든 객체는 기본 유형 또는 참조입니다. 모든 클래스, 열거형, 배열은 참조 유형이며 java.lang.Object에서 상속합니다. 기본 유형은 boolean, byte, short, int, long, char, float 및 double입니다. java.lang.Class는 모든 리플렉션 작업의 진입점입니다. 모든 유형의 객체에 대해 상속 계층.

package com.journaldev.reflection;

public interface BaseInterface {
	
	public int interfaceInt=0;
	
	void method1();
	
	int method2(String str);
}
package com.journaldev.reflection;

public class BaseClass {

	public int baseInt;
	
	private static void method3(){
		System.out.println("Method3");
	}
	
	public int method4(){
		System.out.println("Method4");
		return 0;
	}
	
	public static int method5(){
		System.out.println("Method5");
		return 0;
	}
	
	void method6(){
		System.out.println("Method6");
	}
	
	// inner public class
	public class BaseClassInnerClass{}
		
	//member public enum
	public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {

	public int publicInt;
	private String privateString="private string";
	protected boolean protectedBoolean;
	Object defaultObject;
	
	public ConcreteClass(int i){
		this.publicInt=i;
	}

	@Override
	public void method1() {
		System.out.println("Method1 impl.");
	}

	@Override
	public int method2(String str) {
		System.out.println("Method2 impl.");
		return 0;
	}
	
	@Override
	public int method4(){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	public int method5(int i){
		System.out.println("Method4 overriden.");
		return 0;
	}
	
	// inner classes
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	//member enum
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	//member interface
	public interface ConcreteClassPublicInterface{}

}

클래스에 대한 몇 가지 중요한 refection 방법을 살펴보겠습니다.

클래스 개체 가져오기

정적 변수 class, 객체의 getClass() 메서드 및 java.lang.Class.forName(String 완전히 분류된 클래스 이름). 기본 유형 및 배열의 경우 정적 변수 class를 사용할 수 있습니다. 래퍼 클래스는 클래스를 가져오기 위해 다른 정적 변수 TYPE을 제공합니다.

// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// below method is used most of the times in frameworks like JUnit
	//Spring dependency injection, Tomcat web container
	//Eclipse auto completion of method names, hibernate, Struts2 etc.
	//because ConcreteClass is not available at compile time
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//for primitive types, wrapper classes and arrays
Class<?> booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class<?> cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class<?> cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class<?> twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]

getCanonicalName()은 기본 클래스의 정식 이름을 반환합니다. java.lang.Class는 제네릭을 사용하며 프레임워크에서 검색된 클래스가 프레임워크 기본 클래스의 하위 클래스인지 확인하는 데 도움이 됩니다. 제네릭 및 해당 와일드카드에 대해 알아보려면 Java Generics Tutorial을 확인하세요.

슈퍼 클래스 받기

Class 객체의 getSuperclass() 메서드는 클래스의 슈퍼 클래스를 반환합니다. 이 Class가 Object 클래스, 인터페이스, 기본 유형 또는 void를 나타내는 경우 null이 반환됩니다. 이 객체가 배열 클래스를 나타내는 경우 Object 클래스를 나타내는 Class 객체가 반환됩니다.

Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"

공개 멤버 클래스 가져오기

객체의 클래스 표현의 getClasses() 메서드는 이 클래스 객체가 나타내는 클래스의 구성원인 모든 공개 클래스, 인터페이스 및 열거형을 나타내는 클래스 객체를 포함하는 배열을 반환합니다. 여기에는 슈퍼클래스에서 상속된 공용 클래스 및 인터페이스 멤버와 클래스에서 선언한 공용 클래스 및 인터페이스 멤버가 포함됩니다. 이 메서드는 이 Class 개체에 공용 멤버 클래스 또는 인터페이스가 없거나 이 Class 개체가 기본 유형, 배열 클래스 또는 void를 나타내는 경우 길이 0의 배열을 반환합니다.

Class<?>[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

선언된 클래스 가져오기

getDeclaredClasses() 메서드는 이 Class 객체가 나타내는 클래스의 멤버로 선언된 모든 클래스와 인터페이스를 반영하는 Class 객체의 배열을 반환합니다. 반환된 배열에는 상속된 클래스 및 인터페이스에서 선언된 클래스가 포함되지 않습니다.

//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

선언 클래스 가져오기

getDeclaringClass() 메서드는 선언된 클래스를 나타내는 Class 개체를 반환합니다.

Class<?> innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

패키지 이름 얻기

getPackage() 메서드는 이 클래스의 패키지를 반환합니다. 이 클래스의 클래스 로더는 패키지를 찾는 데 사용됩니다. Package의 getName() 메서드를 호출하여 패키지 이름을 가져올 수 있습니다.

//prints "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

클래스 수정자 얻기

getModifiers() 메서드는 클래스 수정자의 int 표현을 반환합니다. java.lang.reflect.Modifier.toString() 메서드를 사용하여 다음과 같은 문자열 형식으로 가져올 수 있습니다. 소스코드에서 사용.

System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

유형 매개변수 가져오기

getTypeParameters()는 클래스와 연결된 Type 매개변수가 있는 경우 TypeVariable의 배열을 반환합니다. 타입 매개변수는 선언된 순서대로 반환됩니다.

//Get Type parameters (generics)
TypeVariable<?>[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable<?> t : typeParameters)
System.out.print(t.getName()+",");

구현된 인터페이스 가져오기

getGenericInterfaces() 메서드는 일반 유형 정보와 함께 클래스에 의해 구현된 인터페이스 배열을 반환합니다. 또한 getInterfaces()를 사용하여 구현된 모든 인터페이스의 클래스 표현을 가져올 수 있습니다.

Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map<K, V>, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));		

모든 공용 메서드 가져오기

getMethods() 메서드는 슈퍼클래스 및 슈퍼 인터페이스의 공개 메서드를 포함하여 클래스의 공개 메서드 배열을 반환합니다.

Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//prints public methods of ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));

모든 공개 생성자 가져오기

getConstructors() 메서드는 개체 클래스 참조의 공용 생성자 목록을 반환합니다.

//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

모든 공용 필드 가져오기

getFields() 메서드는 슈퍼 클래스 및 슈퍼 인터페이스의 공개 필드를 포함하여 클래스의 공개 필드 배열을 반환합니다.

//Get All public fields
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//prints public fields of ConcreteClass, it's superclass and super interfaces
System.out.println(Arrays.toString(publicFields));

모든 주석 가져오기

getAnnotations() 메서드는 요소에 대한 모든 주석을 반환하며 클래스, 필드 및 메서드에서도 사용할 수 있습니다. 리플렉션과 함께 사용할 수 있는 주석만 RUNTIME의 보존 정책을 사용한다는 점에 유의하십시오. Java 주석 자습서를 확인하십시오. 이에 대해서는 이후 섹션에서 자세히 살펴보겠습니다.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));

필드에 대한 Java 리플렉션

Reflection API는 클래스 필드를 분석하고 런타임에 해당 값을 수정하는 여러 가지 방법을 제공합니다. 이 섹션에서는 메서드에 일반적으로 사용되는 일부 반영 함수를 살펴보겠습니다.

공개 필드 가져오기

마지막 섹션에서는 클래스의 모든 공개 필드 목록을 가져오는 방법을 살펴보았습니다. Reflection API는 getField() 메소드를 통해 클래스의 특정 공개 필드를 가져오는 메소드도 제공합니다. 이 메서드는 지정된 클래스 참조, 슈퍼 인터페이스, 슈퍼 클래스에서 필드를 찾습니다.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");

위의 호출은 ConcreteClass에 의해 구현된 BaseInterface에서 필드를 반환합니다. 필드가 없으면 NoSuchFieldException이 발생합니다.

필드 선언 클래스

필드를 선언하는 클래스를 얻기 위해 필드 객체의 getDeclaringClass()를 사용할 수 있습니다.

try {
	Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
	Class<?> fieldClass = field.getDeclaringClass();
	System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
	e.printStackTrace();
}

필드 유형 가져오기

getType() 메서드는 선언된 필드 유형에 대한 클래스 객체를 반환합니다. 필드가 기본 유형인 경우 래퍼 클래스 객체를 반환합니다.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int			

공용 필드 값 가져오기/설정

리플렉션을 사용하여 객체의 필드 값을 가져오고 설정할 수 있습니다.

Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

get() 메서드는 객체를 반환하므로 필드가 기본 유형인 경우 해당 래퍼 클래스를 반환합니다. 필드가 정적이면 get() 메서드에서 Object를 null로 전달할 수 있습니다. Object를 필드로 설정하거나 다른 유형의 기본 유형을 필드로 설정하는 여러 set*() 메소드가 있습니다. 필드 유형을 얻은 다음 올바른 함수를 호출하여 필드 값을 올바르게 설정할 수 있습니다. 필드가 최종 필드인 경우 set() 메소드는 java.lang.IllegalAccessException을 발생시킵니다.

비공개 필드 값 가져오기/설정

비공개 필드와 메서드는 클래스 외부에서 액세스할 수 없다는 것을 알고 있지만 리플렉션을 사용하면 필드 수정자에 대한 자바 액세스 확인을 해제하여 비공개 필드 값을 가져오거나 설정할 수 있습니다.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"

메소드에 대한 Java 리플렉션

리플렉션을 사용하면 메서드에 대한 정보를 얻을 수 있고 메서드를 호출할 수도 있습니다. 이 섹션에서는 메서드를 가져오고, 메서드를 호출하고, 개인 메서드에 액세스하는 다양한 방법을 배웁니다.

공개 메서드 가져오기

getMethod()를 사용하여 클래스의 공개 메서드를 가져올 수 있으며 메서드 이름과 메서드의 매개 변수 유형을 전달해야 합니다. 메소드가 클래스에서 발견되지 않으면 리플렉션 API는 슈퍼클래스에서 메소드를 찾습니다. 아래 예제에서는 리플렉션을 사용하여 HashMap의 put() 메서드를 가져옵니다. 이 예제는 매개변수 유형, 메소드 수정자 및 메소드의 리턴 유형을 가져오는 방법도 보여줍니다.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

공개 메서드 호출

Method 객체의 invoke() 메소드를 사용하여 메소드를 호출할 수 있습니다. 아래 예제 코드에서는 리플렉션을 사용하여 HashMap에서 put 메소드를 호출합니다.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

메서드가 정적이면 객체 인수로 NULL을 전달할 수 있습니다.

개인 메소드 호출

getDeclaredMethod()를 사용하여 개인 메서드를 가져온 다음 액세스 확인을 해제하여 호출할 수 있습니다. 아래 예제는 정적이고 매개 변수가 없는 BaseClass의 method3()을 호출하는 방법을 보여줍니다.

//invoking private method
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"

생성자를 위한 Java 리플렉션

Reflection API는 분석할 클래스의 생성자를 가져오는 메서드를 제공하며 생성자를 호출하여 클래스의 새 인스턴스를 만들 수 있습니다. 우리는 이미 모든 공개 생성자를 얻는 방법을 배웠습니다.

공개 생성자 가져오기

객체의 클래스 표현에서 getConstructor() 메서드를 사용하여 특정 공개 생성자를 얻을 수 있습니다. 아래 예제에서는 위에서 정의한 ConcreteClass의 생성자와 HashMap의 인수 없는 생성자를 가져오는 방법을 보여줍니다. 또한 생성자의 매개변수 유형 배열을 가져오는 방법도 보여줍니다.

Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

생성자를 사용하여 개체 인스턴스화

생성자 객체에서 newInstance() 메서드를 사용하여 클래스의 새 인스턴스를 인스턴스화할 수 있습니다. 컴파일 타임에 클래스 정보가 없을 때 리플렉션을 사용하기 때문에 객체에 할당한 다음 리플렉션을 사용하여 해당 필드에 액세스하고 메서드를 호출할 수 있습니다.

Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
		
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);

주석에 대한 반영

주석은 클래스, 메소드 또는 필드의 메타데이터 정보를 제공하기 위해 Java 1.5에 도입되었으며 현재 Spring 및 Hibernate와 같은 프레임워크에서 많이 사용됩니다. 리플렉션 API도 확장되어 런타임 시 주석 분석을 지원합니다. 리플렉션 API를 사용하여 보존 정책이 런타임인 주석을 분석할 수 있습니다. 주석에 대한 자세한 자습서와 리플렉션 API를 사용하여 주석을 구문 분석하는 방법을 이미 작성했으므로 Java 주석 자습서를 확인하는 것이 좋습니다. 여기까지가 Java Reflection 예제 자습서의 전부입니다. 자습서가 마음에 들었고 Java Reflection API의 중요성을 이해하셨기를 바랍니다.