CodeGym /Java Blog /무작위의 /리플렉션 API: 리플렉션. 자바의 어두운 면
John Squirrels
레벨 41
San Francisco

리플렉션 API: 리플렉션. 자바의 어두운 면

무작위의 그룹에 게시되었습니다
안녕, 젊은 파다완. 이 기사에서는 Java 프로그래머가 불가능해 보이는 상황에서만 사용하는 힘인 Force에 대해 설명합니다. Java의 어두운 면은 Reflection API입니다. Java에서 리플렉션은 Java Reflection API를 사용하여 구현됩니다.

자바 리플렉션이란?

인터넷에는 짧고 정확하며 대중적인 정의가 있습니다. 리플렉션( 라틴어 후기 reflexio에서 되돌리다 )은 실행 중인 프로그램에 대한 데이터를 탐색하는 메커니즘입니다. 리플렉션을 사용하면 필드, 메서드 및 클래스 생성자에 대한 정보를 탐색할 수 있습니다. 리플렉션을 사용하면 컴파일 시간에는 없었지만 런타임 동안 사용할 수 있게 된 형식으로 작업할 수 있습니다. 오류 정보를 발행하기 위한 반영 및 논리적으로 일관된 모델을 통해 올바른 동적 코드를 생성할 수 있습니다. 즉, Java에서 리플렉션이 작동하는 방식을 이해하면 많은 놀라운 기회가 열립니다. 문자 그대로 클래스와 해당 구성 요소를 저글링할 수 있습니다. 리플렉션이 허용하는 기본 목록은 다음과 같습니다.
  • 객체의 클래스를 학습/결정합니다.
  • 클래스의 수정자, 필드, 메서드, 상수, 생성자 및 슈퍼클래스에 대한 정보를 얻습니다.
  • 구현된 인터페이스에 속하는 메소드를 찾으십시오.
  • 런타임까지 클래스 이름을 알 수 없는 클래스의 인스턴스를 만듭니다.
  • 이름으로 개체 필드의 값을 가져오고 설정합니다.
  • 객체의 메서드를 이름으로 호출합니다.
리플렉션은 거의 모든 최신 Java 기술에서 사용됩니다. 플랫폼으로서의 Java가 성찰 없이 이렇게 광범위하게 채택될 수 있었다는 것은 상상하기 어렵습니다. 아마도 그렇지 않았을 것입니다. 이제 일반적으로 이론적 개념으로 성찰에 익숙해졌으므로 실제 적용을 진행하겠습니다! Reflection API의 모든 메서드를 배우지 않고 실제로 실제로 접하게 될 메서드만 배우겠습니다. 리플렉션에는 클래스 작업이 포함되므로 다음과 같은 간단한 클래스부터 시작합니다 MyClass.

public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
보시다시피 이것은 매우 기본적인 클래스입니다. 매개변수가 있는 생성자는 의도적으로 주석 처리됩니다. 그것에 대해서는 나중에 다시 다루겠습니다. 클래스의 내용을 주의 깊게 살펴보면 name 필드 에 대한 getter가 없음을 알 수 있습니다 . 이름 필드 자체는 개인 액세스 수정자로 표시됩니다 . 클래스 자체 외부에서는 액세스할 수 없습니다. 즉, 해당 값을 검색할 수 없습니다 . " 그래서 뭐가 문제야 ?" 당신은 말한다. " 게터 추가 또는 액세스 수정자 변경". 그리고 당신이 옳을 것입니다.MyClass컴파일된 AAR 라이브러리 또는 변경할 수 없는 다른 개인 모듈에 있었습니다. 실제로 이것은 항상 발생합니다. 그리고 일부 부주의한 프로그래머는 단순히 getter를 작성하는 것을 잊었습니다 . 이것은 성찰을 기억할 바로 그 시간입니다! 클래스 의 개인 이름 필드에 접근해 봅시다 MyClass.

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; // No getter =(
   System.out.println(number + name); // Output: 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name); // Output: 0default
}
방금 일어난 일을 분석해 봅시다. Java에는 이라는 멋진 클래스가 있습니다 Class. 실행 가능한 Java 애플리케이션의 클래스 및 인터페이스를 나타냅니다. 이 기사의 주제가 아니기 때문에 Classand 사이의 관계는 다루지 않을 것입니다 . ClassLoader다음으로 이 클래스의 필드를 검색하려면 메서드를 호출해야 합니다 getFields(). 이 메서드는 이 클래스의 액세스 가능한 모든 필드를 반환합니다. 필드가 private 이기 때문에 이 방법은 작동하지 않으므로 이 방법을 사용합니다 getDeclaredFields(). 이 메서드는 또한 클래스 필드의 배열을 반환하지만 이제 개인보호 필드를 포함합니다. 이 경우 관심 있는 필드의 이름을 알고 있으므로 메서드를 사용할 수 있습니다 getDeclaredField(String). 여기서String원하는 필드의 이름입니다. 메모: getFields()getDeclaredFields()부모 클래스의 필드를 반환하지 마십시오 ! 엄청난. 이름을Field 참조하는 개체가 있습니다 . 필드가 public 이 아니므로 작업하려면 액세스 권한을 부여해야 합니다. 이 방법을 사용하면 더 진행할 수 있습니다. 이제 이름 필드는 우리가 완전히 제어할 수 있습니다! 개체의 메서드를 호출하여 해당 값을 검색할 수 있습니다 . 여기서 는 클래스의 인스턴스입니다 . 유형을 변환 하고 이름 변수 에 값을 할당합니다 . 이름 필드에 새 값을 설정할 setter를 찾을 수 없으면 set 메서드를 사용할 수 있습니다. setAccessible(true)Fieldget(Object)ObjectMyClassString

field.set(myClass, (String) "new value");
축하해요! 리플렉션의 기본 사항을 마스터하고 비공개 필드에 액세스했습니다! try/catch블록과 처리되는 예외 유형 에 주의하십시오 . IDE는 그 자체로 존재가 필요하다고 말하지만 이름으로 그들이 여기에 있는 이유를 명확하게 알 수 있습니다. 계속! 알다시피 MyClass클래스에는 클래스 데이터에 대한 정보를 표시하는 메서드가 이미 있습니다.

private void printData(){
       System.out.println(number + name);
   }
하지만 이 프로그래머는 여기에도 지문을 남겼습니다. 메서드에는 전용 액세스 수정자가 있으며 매번 데이터를 표시하려면 자체 코드를 작성해야 합니다. 정말 엉망이야. 우리의 성찰은 어디로 갔습니까? 다음 함수를 작성합니다.

public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
여기서 절차는 필드 검색에 사용되는 절차와 거의 동일합니다. 이름으로 원하는 메서드에 액세스하고 액세스 권한을 부여합니다. 그리고 개체에서 Method우리는 메서드를 호출합니다 invoke(Object, Args). 여기서 메서드 는 클래스 Object의 인스턴스이기도 합니다 MyClass. Args메서드의 인수이지만 우리는 인수가 없습니다. 이제 이 기능을 사용하여 printData정보를 표시합니다.

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// Output: 0new value
}
만세! 이제 클래스의 전용 메서드에 액세스할 수 있습니다. 그러나 메서드에 인수가 있고 생성자가 주석 처리된 이유는 무엇입니까? 모든 것이 정해진 시간 안에 있습니다. 리플렉션을 사용하면 런타임 (프로그램이 실행되는 동안)에 클래스 의 인스턴스를 만들 수 있다는 것이 처음 정의에서 분명합니다 ! 클래스의 전체 이름을 사용하여 개체를 만들 수 있습니다. 클래스의 전체 이름은 해당 패키지 의 경로를 포함한 클래스 이름입니다 .
리플렉션 API: 리플렉션.  자바의 어두운 면 - 2
패키지 계층 구조에서 MyClass 의 전체 이름은 "reflection.MyClass"입니다. 클래스 이름을 배우는 간단한 방법도 있습니다(클래스 이름을 문자열로 반환).

MyClass.class.getName()
Java 리플렉션을 사용하여 클래스의 인스턴스를 생성해 보겠습니다.

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Java 애플리케이션이 시작될 때 모든 클래스가 JVM에 로드되지는 않습니다. MyClass코드가 클래스를 참조하지 않는 경우 ClassLoader클래스를 JVM으로 로드하는 은(는) 클래스를 로드하지 않습니다. 즉, 강제 ClassLoader로 로드하고 클래스 설명을 변수 형식으로 가져와야 합니다 Class. 이것이 설명이 필요한 클래스의 이름인 forName(String)메서드가 있는 이유입니다 . String개체를 가져온 후 Сlass메서드를 호출하면 해당 설명을 사용하여 만든 개체가 newInstance()반환됩니다 . Object남은 것은 이 개체를 우리에게 제공하는 것입니다.MyClass수업. 시원한! 어려웠지만 이해할 수 있기를 바랍니다. 이제 문자 그대로 한 줄에 클래스의 인스턴스를 만들 수 있습니다! 안타깝게도 설명된 접근 방식은 기본 생성자(매개 변수 없음)에서만 작동합니다. 매개변수를 사용하여 메서드와 생성자를 어떻게 호출합니까? 이제 생성자의 주석을 제거할 시간입니다. 예상대로 newInstance()기본 생성자를 찾을 수 없으며 더 이상 작동하지 않습니다. 클래스 인스턴스화를 다시 작성해 보겠습니다.

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
getConstructors()메소드는 클래스 생성자를 얻기 위해 클래스 정의에서 호출되어야 하며 getParameterTypes()생성자의 매개변수를 얻기 위해 호출되어야 합니다.

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
모든 생성자와 해당 매개 변수를 가져옵니다. 내 예제에서는 이전에 알려진 특정 매개 변수가 있는 특정 생성자를 참조합니다. newInstance그리고 이 생성자를 호출하기 위해 이러한 매개 변수의 값을 전달하는 메서드를 사용합니다 . invoke메서드를 호출하는 데 사용할 때도 마찬가지입니다 . 이것은 다음과 같은 질문을 합니다. 리플렉션을 통해 생성자를 호출하는 것이 언제 유용할까요? 처음에 이미 언급했듯이 최신 Java 기술은 Java Reflection API 없이는 얻을 수 없습니다. 예를 들어 DI(Dependency Injection)는 인기 있는 Darer를 형성하기 위해 메서드 및 생성자의 리플렉션과 주석을 결합합니다.안드로이드 개발을 위한 라이브러리. 이 기사를 읽고 나면 Java Reflection API 방식에 대해 교육을 받았다고 자신 있게 생각할 수 있습니다. 그들은 리플렉션을 괜히 자바의 어두운 면이라고 부르지 않습니다. OOP 패러다임을 완전히 깨뜨립니다. Java에서 캡슐화는 특정 프로그램 구성 요소에 대한 다른 사람의 액세스를 숨기고 제한합니다. private 한정자를 사용하는 경우 해당 필드가 존재하는 클래스 내에서만 액세스할 수 있도록 합니다. 그리고 이 원칙에 따라 프로그램의 후속 아키텍처를 구축합니다. 이 기사에서는 리플렉션을 사용하여 어디로든 강제로 이동하는 방법을 살펴보았습니다. 창조적인 디자인 패턴 싱글톤아키텍처 솔루션으로서의 좋은 예입니다. 기본 아이디어는 이 패턴을 구현하는 클래스가 전체 프로그램을 실행하는 동안 인스턴스를 하나만 갖는다는 것입니다. 이는 개인 액세스 수정자를 기본 생성자에 추가하여 수행됩니다. 그리고 프로그래머가 이러한 클래스의 인스턴스를 더 많이 생성하지 않고 리플렉션을 사용한다면 매우 나쁠 것입니다. 그런데 최근 동료가 매우 흥미로운 질문을 하는 것을 들었습니다. Singleton 패턴을 구현하는 클래스를 상속할 수 있습니까? 이 경우 반성조차 무력할 수 있을까? 기사에 대한 피드백과 답변을 아래 댓글에 남겨주시고 거기에서 질문을 해보세요!

더 읽어보기:

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION