`Java Reflection` 예제 튜토리얼

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

자바 Reflection

  1. 자바 Reflection
  2. 클래스에 대한 자바 리플렉션
  3. 필드에 대한 Java Reflection
  4. 메서드에 대한 Java Reflection
  5. 생성자에 대한 Java Reflection
  6. 주석을 위한 자바 리플렉션

  1. 자바에서의 리플렉션

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

  1. JUnit – 리플렉션을 사용하여 @Test 주석을 구문 분석하여 테스트 메서드를 가져오고 호출합니다.
  2. Spring – 의존성 주입, 자세한 내용은 Spring Dependency Injection에서 확인할 수 있습니다.
  3. Tomcat – 웹 컨테이너는 웹.xml 파일과 요청 URI를 구문 분석하여 올바른 모듈로 요청을 전달합니다.
  4. Eclipse – 메서드 이름 자동 완성
  5. Struts
  6. Hibernate

리스트는 끝이 없으며, 이 모든 프레임워크는 사용자 정의 클래스, 인터페이스, 메서드 등에 대한 지식과 접근 권한이 없기 때문에 자바 리플렉션을 사용합니다. 이미 클래스와 인터페이스에 액세스할 수 있는 일반 프로그래밍에서는 리플렉션을 사용해서는 안 됩니다. 왜냐하면 다음과 같은 단점이 있기 때문입니다.

  • 성능 저하 – 자바 리플렉션은 타입을 동적으로 해결하기 때문에 클래스를 로드하기 위해 클래스 경로를 스캔하는 등의 처리가 필요하며, 이로 인해 성능이 느려질 수 있습니다.
  • 보안 제한 – 리플렉션은 런타임 권한이 필요하며, 시스템이 보안 관리자 아래에서 실행 중인 경우 이러한 권한을 사용할 수 없을 수 있습니다. 이는 보안 관리자로 인해 애플리케이션이 런타임에 실패할 수 있습니다.
  • 보안 문제 – 리플렉션을 사용하면 접근하면 안 되는 코드 일부에 액세스할 수 있습니다. 예를 들어 클래스의 private 필드에 액세스하고 값을 변경할 수 있습니다. 이는 심각한 보안 위협이 될 수 있으며 애플리케이션이 비정상적으로 동작할 수 있습니다.
  • 유지보수의 어려움 – 리플렉션 코드는 이해하고 디버깅하기 어려우며, 코드의 문제를 컴파일 시간에 찾을 수 없습니다. 클래스가 사용할 수 없을 수 있기 때문에 유연성이 떨어지고 유지보수가 어렵습니다.
  1. 클래스를 위한 Java Reflection

자바에서 모든 객체는 기본 타입 또는 참조 타입 중 하나입니다. 모든 클래스, 열거형, 배열은 참조 타입이며 java.lang.Object을 상속합니다. 기본 타입은 – boolean, byte, short, int, long, char, float, double입니다. java.lang.Class는 모든 리플렉션 작업의 진입점입니다. 모든 객체 타입에 대해 JVMjava.lang.Class불변 인스턴스를 생성하며, 이 인스턴스는 객체의 런타임 속성을 검사하고 새로운 객체를 생성하며, 메소드를 호출하고 객체 필드를 가져오거나 설정하는 메소드를 제공합니다. 이 섹션에서는 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");
	}
	
	// 내부 공용 클래스
	public class BaseClassInnerClass{}
		
	 // 멤버 공용 열거형
	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;
	}
	
	 // 내부 클래스
	public class ConcreteClassPublicClass{}
	private class ConcreteClassPrivateClass{}
	protected class ConcreteClassProtectedClass{}
	class ConcreteClassDefaultClass{}
	
	 // 멤버 열거형
	enum ConcreteClassDefaultEnum{}
	public enum ConcreteClassPublicEnum{}
	
	 // 멤버 인터페이스
	public interface ConcreteClassPublicInterface{}

}

클래스에 대한 중요한 리플렉션 메소드 몇 가지를 살펴보겠습니다.

클래스 객체 가져오기

객체의 클래스를 가져오는 방법은 세 가지이며, 정적 변수 class를 통해, 객체의 getClass() 메소드를 사용하여 그리고 java.lang.Class.forName(String fullyClassifiedClassName)을 사용하여 가져올 수 있습니다. 기본 타입과 배열에 대해서는 정적 변수 class를 사용할 수 있습니다. 래퍼 클래스는 클래스를 가져오기 위해 또 다른 정적 변수 TYPE을 제공합니다.

// 리플렉션을 사용하여 클래스 가져오기
Class concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
	// 아래 메소드는 주로 JUnit과 같은 프레임워크에서 사용됩니다.
	//Spring 의존성 주입, Tomcat 웹 컨테이너
	//Eclipse 메소드 이름 자동 완성, hibernate, Struts2 등
	//ConcreteClass가 컴파일 시간에 사용할 수 없기 때문에
	concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//기본 타입, 래퍼 클래스 및 배열에 대해서는
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 제네릭 자습서를 확인하세요.

슈퍼 클래스 가져오기

getSuperclass() 메서드는 Class 객체의 슈퍼 클래스를 반환합니다. 이 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 표현을 나타내는 클래스가 가지고 있는 모든 public 클래스, 인터페이스 및 열거형을 나타내는 Class 객체의 배열을 반환합니다. 이에는 상위 클래스에서 상속받은 public 클래스 및 인터페이스 멤버와 클래스에서 선언한 public 클래스 및 인터페이스 멤버가 포함됩니다. 이 메서드는 이 Class 객체가 public 멤버 클래스 또는 인터페이스가 없거나 원시 타입, 배열 클래스 또는 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 객체의 배열을 반환합니다. 반환된 배열에는 상속받은 클래스와 인터페이스에서 선언된 클래스는 포함되지 않습니다.

//ConcreteClass
Class[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
에서 명시적으로 선언된 모든 클래스, 인터페이스 및 열거형을 가져옵니다.//[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 innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
 //com.journaldev.reflection.ConcreteClass을 출력합니다. 
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

패키지 이름 가져오기

getPackage() 메소드는이 클래스에 대한 패키지를 반환합니다. 이 클래스의 클래스 로더를 사용하여 패키지를 찾을 수 있습니다. 패키지의 이름을 얻기 위해 Package의 getName() 메소드를 호출할 수 있습니다.

// "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"
// "public abstract interface"를 출력합니다
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

타입 매개변수 가져오기

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

// 타입 매개변수(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, 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();
//ConcreteClass, BaseClass, Object의 공개 메소드를 출력합니다.
System.out.println(Arrays.toString(publicMethods));

모든 공개 생성자 가져오기

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

//모든 공개 생성자 가져오기
Constructor[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//ConcreteClass의 공개 생성자를 출력합니다.
System.out.println(Arrays.toString(publicConstructors));

모든 공개 필드 가져오기

getFields() 메소드는 해당 클래스의 공개 필드 배열을 반환하며, 슈퍼 클래스와 슈퍼 인터페이스의 공개 필드도 포함됩니다.

//모든 public 필드 가져오기
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//ConcreteClass, 그의 슈퍼 클래스 및 슈퍼 인터페이스의 public 필드를 출력합니다
System.out.println(Arrays.toString(publicFields));

모든 어노테이션 가져오기

getAnnotations() 메서드는 요소에 대한 모든 어노테이션을 반환합니다. 이 메서드는 클래스, 필드 및 메서드와 함께 사용할 수 있습니다. 참고로, 리플렉션으로 사용 가능한 어노테이션은 RUNTIME 보존 정책을 가지고 있습니다. 자세한 내용은 Java 어노테이션 튜토리얼을 확인하세요. 이에 대해서는 나중에 더 자세히 살펴보겠습니다.

java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//[@java.lang.Deprecated()]을 출력합니다
System.out.println(Arrays.toString(annotations));
  1. 필드에 대한 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() 메소드는 선언된 필드 유형의 Class 객체를 반환하며, 필드가 기본 타입인 경우 래퍼 클래스 객체를 반환합니다.

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() 메서드는 Object를 반환하므로, 필드가 기본 타입인 경우 해당하는 래퍼 클래스를 반환합니다. 필드가 정적(static)인 경우, get() 메서드에 null을 전달할 수 있습니다. 필드에 Object를 설정하거나 다양한 기본 타입을 설정하기 위해 여러 set*() 메서드가 있습니다. 필드의 타입을 가져와서 올바른 함수를 호출하여 필드 값을 올바르게 설정할 수 있습니다. 필드가 final인 경우, set() 메서드는 java.lang.IllegalAccessException을 발생시킵니다.

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

비공개(private) 필드와 메서드는 클래스 외부에서 접근할 수 없지만, 리플렉션을 사용하여 필드 값 가져오기와 설정하기가 가능합니다. 이를 위해 필드 수정자(modifiers)에 대한 자바 접근 검사를 해제할 수 있습니다.

Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//아래 메서드 호출로 접근 검사 해제
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"
  1. 메서드에 대한 자바 리플렉션

리플렉션을 사용하면 메서드에 대한 정보를 얻을 수 있으며, 메서드를 호출할 수도 있습니다. 이 섹션에서는 메서드를 얻는 다양한 방법, 메서드를 호출하는 방법 및 비공개 메서드에 접근하는 방법을 배우게 됩니다.

공개 메서드 가져오기

getClass()를 사용하여 클래스의 공개 메서드를 가져올 수 있습니다. 메서드 이름과 메서드의 매개변수 유형을 전달해야 합니다. 메서드가 클래스에서 찾을 수 없는 경우, 리플렉션 API는 슈퍼클래스에서 메서드를 찾습니다. 아래 예제에서는 리플렉션을 사용하여 HashMap의 put() 메서드를 가져오는 방법을 보여줍니다. 예제에서는 또한 메서드의 매개변수 유형, 메서드의 수정자 및 반환 유형을 가져오는 방법도 보여줍니다.

Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//메서드의 매개변수 유형 가져오기, "[class java.lang.Object, class java.lang.Object]"를 출력합니다.
System.out.println(Arrays.toString(method.getParameterTypes()));
//메서드의 반환 유형 가져오기, "class java.lang.Object"를 반환합니다. void에 대한 클래스 참조
System.out.println(method.getReturnType());
//메서드의 수정자 가져오기
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}

만약 메소드가 정적(static)인 경우에는 객체 인자로 NULL을 전달할 수 있습니다.

비공개 메소드 호출

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

//비공개 메소드 호출
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
  1. 생성자에 대한 자바 리플렉션

Reflection API는 클래스의 생성자를 분석하기 위한 메소드를 제공하며, 생성자를 호출하여 클래스의 새로운 인스턴스를 생성할 수 있습니다. 이미 모든 public 생성자를 가져오는 방법을 배웠습니다.

Public 생성자 가져오기

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

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//생성자 매개변수 가져오기
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() 메소드를 생성자 객체에 사용하여 클래스의 새로운 인스턴스를 인스턴스화할 수 있습니다. 컴파일 시간에 클래스 정보가 없을 때 reflection을 사용하므로, 이를 Object에 할당한 다음 reflection을 사용하여 필드에 액세스하고 메소드를 호출할 수 있습니다.

Constructor constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//생성자 매개변수 가져오기
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 myMap = (HashMap) hashMapConstructor.newInstance(null);
  1. Annotations에 대한 Reflection

Annotations은 Java 1.5에서 클래스, 메서드 또는 필드의 메타데이터 정보를 제공하기 위해 도입되었으며, 이제 Spring과 Hibernate과 같은 프레임워크에서 널리 사용됩니다. Reflection API도 런타임에 주석을 분석하기 위한 지원을 제공하기 위해 확장되었습니다. Reflection API를 사용하여 보존 정책이 Runtime인 주석을 분석할 수 있습니다. 이미 주석과 reflection API를 사용하여 주석을 파싱하는 자세한 튜토리얼을 작성했으므로, Java 주석 튜토리얼을 확인하는 것을 제안합니다. 이것이 Java Reflection API의 중요성을 이해하고 튜토리얼을 좋아하셨기를 바랍니다.

Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial