Java Reflection API에 대해 자세히 살펴보십시오.

지난달의 "Java In-Depth"에서는 원시 클래스 데이터에 대한 액세스 권한이있는 Java 클래스가 클래스 "내부"를 볼 수있는 방법과 클래스가 어떻게 구성되었는지 파악하는 방법에 대해 설명했습니다. 또한 클래스 로더를 추가하면 이러한 클래스를 실행중인 환경에로드하여 실행할 수 있음을 보여주었습니다. 이 예제는 정적 인트로 스펙 션 의 한 형태입니다 . 이번 달에는 Java 클래스에 동적 인트로 스펙 션 (이미로드 된 클래스 내부를 볼 수 있는 기능)을 수행 할 수있는 기능을 제공하는 Java Reflection API 를 살펴 보겠습니다.

자기 성찰의 유용성

Java의 강점 중 하나는 실행중인 환경이 동적으로 변경된다는 가정하에 설계되었다는 것입니다. 클래스는 동적으로로드되고 바인딩은 동적으로 수행되며 객체 인스턴스는 필요할 때 즉시 동적으로 생성됩니다. 역사적으로 매우 역동적이지 않은 것은 "익명"클래스를 조작하는 능력입니다. 이 컨텍스트에서 익명 클래스는 런타임에 Java 클래스에로드되거나 제공되며 이전에 Java 프로그램에서 해당 유형을 알 수 없었던 클래스입니다.

익명 클래스

익명 클래스를 지원하는 것은 설명하기 어렵고 프로그램에서 디자인하기가 더 어렵습니다. 익명 클래스를 지원하는 문제는 다음과 같이 설명 할 수 있습니다. "Java 객체가 주어지면 해당 객체를 계속 작업에 통합 할 수있는 프로그램을 작성하십시오." 일반적인 해결책은 다소 어렵지만 문제를 제한함으로써 일부 전문적인 해결책을 만들 수 있습니다. Java 1.0 버전에서이 문제에 대한 특수 솔루션의 두 가지 예는 Java 애플릿과 Java 인터프리터의 명령 행 버전입니다.

Java 애플릿은 웹 브라우저의 컨텍스트에서 실행중인 JVM (Java Virtual Machine)에 의해로드되고 호출되는 Java 클래스입니다. 런타임은 각 개별 클래스를 호출하는 데 필요한 정보를 미리 알지 못하기 때문에 이러한 Java 클래스는 익명입니다. 그러나 특정 클래스를 호출하는 문제는 Java 클래스를 사용하여 해결됩니다 java.applet.Applet.

과 같은 일반적인 슈퍼 클래스와 같은 AppletJava 인터페이스 AppletContext는 이전에 합의 된 계약을 생성하여 익명 클래스의 문제를 해결합니다. 특히 런타임 환경 공급자는 지정된 인터페이스를 준수하는 모든 개체를 사용할 수 있다고 광고하고 런타임 환경 소비자는 런타임에 제공하려는 모든 개체에서 지정된 인터페이스를 사용합니다. 애플릿의 경우 잘 지정된 인터페이스가 공통 수퍼 클래스의 형태로 존재합니다.

특히 다중 상속이없는 경우 공통 슈퍼 클래스 솔루션의 단점은 해당 시스템이 전체 계약을 구현하지 않는 한 환경에서 실행되도록 빌드 된 개체를 다른 시스템에서도 사용할 수 없다는 것입니다. Applet인터페이스 의 경우 호스팅 환경에서 AppletContext. 이것이 애플릿 솔루션에 대해 의미하는 것은 솔루션이 애플릿을로드 할 때만 작동한다는 것입니다. Hashtable웹 페이지에 개체 의 인스턴스를 배치하고 브라우저에서 해당 인스턴스를 가리키면 애플릿 시스템이 제한된 범위 밖에서 작동 할 수 없기 때문에로드되지 않습니다.

애플릿 예제 외에도 introspection은 지난달에 언급 한 문제를 해결하는 데 도움이됩니다. Java 가상 머신의 명령 행 버전이 방금로드 한 클래스에서 실행을 시작하는 방법을 알아내는 것입니다. 이 예에서 가상 머신은로드 된 클래스에서 일부 정적 메서드를 호출해야합니다. 관례 적으로이 메서드는 이름이 지정 main되고 String개체 배열 인 단일 인수를 사용 합니다.

보다 동적 인 솔루션에 대한 동기

기존 Java 1.0 아키텍처의 문제는로드 가능한 UI 구성 요소, Java 기반 OS의로드 가능한 장치 드라이버 및 동적으로 구성 가능한 편집 환경과 같은보다 동적 인 인트로 스펙 션 환경으로 해결할 수있는 문제가 있다는 것입니다. "킬러 앱"또는 Java Reflection API를 생성하게 만든 문제는 Java 용 개체 구성 요소 모델 개발이었습니다. 이 모델은 이제 JavaBeans로 알려져 있습니다.

사용자 인터페이스 구성 요소는 서로 다른 두 개의 소비자가 있기 때문에 내부 검사 시스템에 이상적인 디자인 포인트입니다. 한편으로 구성 요소 개체는 함께 연결되어 일부 응용 프로그램의 일부로 사용자 인터페이스를 형성합니다. 또는 구성 요소가 무엇인지 알지 못하거나 더 중요한 것은 구성 요소의 소스 코드에 액세스하지 않고도 사용자 구성 요소를 조작하는 도구를위한 인터페이스가 필요합니다.

Java Reflection API는 JavaBeans 사용자 인터페이스 구성 요소 API의 요구에서 비롯되었습니다.

반사 란 무엇입니까?

기본적으로 Reflection API는 클래스 파일의 다양한 부분을 나타내는 개체와 이러한 개체를 안전하고 안전한 방식으로 추출하는 수단의 두 가지 구성 요소로 구성됩니다. 후자는 Java가 많은 보안 보호 장치를 제공하기 때문에 매우 중요하며 이러한 보호 장치를 무효화 한 클래스 집합을 제공하는 것은 의미가 없습니다.

Reflection API의 첫 번째 구성 요소는 클래스에 대한 정보를 가져 오는 데 사용되는 메커니즘입니다. 이 메커니즘은라는 클래스에 내장되어 Class있습니다. 특수 클래스 Class는 Java 시스템 내의 객체를 설명하는 메타 정보의 범용 유형입니다. Java 시스템의 클래스 로더는 유형의 객체를 반환합니다 Class. 지금까지이 클래스에서 가장 흥미로운 세 가지 방법은 다음과 같습니다.

  • forName, 현재 클래스 로더를 사용하여 주어진 이름의 클래스를로드합니다.

  • getName, 클래스 이름을 String객체 로 반환하므로 클래스 이름으로 객체 참조를 식별하는 데 유용했습니다.

  • newInstance, 클래스에서 null 생성자를 호출하고 (존재하는 경우) 해당 객체 클래스의 객체 인스턴스를 반환합니다.

이 세 가지 유용한 메서드에 Reflection API는 클래스에 몇 가지 추가 메서드를 추가합니다 Class. 다음과 같습니다.

  • getConstructor, getConstructors,getDeclaredConstructor
  • getMethod, getMethods,getDeclaredMethods
  • getField, getFields,getDeclaredFields
  • getSuperclass
  • getInterfaces
  • getDeclaredClasses

In addition to these methods, many new classes were added to represent the objects that these methods would return. The new classes mostly are part of the java.lang.reflect package, but some of the new basic type classes (Void, Byte, and so on) are in the java.lang package. The decision was made to put the new classes where they are by putting classes that represented meta-data in the reflection package and classes that represented types in the language package.

Thus, the Reflection API represents a number of changes to class Class that let you ask questions about the internals of the class, and a bunch of classes that represent the answers that these new methods give you.

How do I use the Reflection API?

The question "How do I use the API?" is perhaps the more interesting question than "What is reflection?"

The Reflection API is symmetric, which means that if you are holding a Class object, you can ask about its internals, and if you have one of the internals, you can ask it which class declared it. Thus you can move back and forth from class to method to parameter to class to method, and so on. One interesting use of this technology is to find out most of the interdependencies between a given class and the rest of the system.

A working example

On a more practical level, however, you can use the Reflection API to dump out a class, much as my dumpclass class did in last month's column.

To demonstrate the Reflection API, I wrote a class called ReflectClass that would take a class known to the Java run time (meaning it is in your class path somewhere) and, through the Reflection API, dump out its structure to the terminal window. To experiment with this class, you will need to have a 1.1 version of the JDK available.

Note: Do not try to use a 1.0 run time as it gets all confused, usually resulting in an incompatible class change exception.

The class ReflectClass begins as follows:

import java.lang.reflect.*; import java.util.*; public class ReflectClass { 

As you can see above, the first thing the code does is import the Reflection API classes. Next, it jumps right into the main method, which starts out as shown below.

 public static void main(String args[]) { Constructor cn[]; Class cc[]; Method mm[]; Field ff[]; Class c = null; Class supClass; String x, y, s1, s2, s3; Hashtable classRef = new Hashtable(); if (args.length == 0) { System.out.println("Please specify a class name on the command line."); System.exit(1); } try { c = Class.forName(args[0]); } catch (ClassNotFoundException ee) { System.out.println("Couldn't find class '"+args[0]+"'"); System.exit(1); } 

The method main declares arrays of constructors, fields, and methods. If you recall, these are three of the four fundamental parts of the class file. The fourth part is the attributes, which the Reflection API unfortunately does not give you access to. After the arrays, I've done some command-line processing. If the user has typed a class name, the code attempts to load it using the forName method of class Class. The forName method takes Java class names, not file names, so to look inside the java.math.BigInteger class, you simply type "java ReflectClass java.math.BigInteger," rather than point out where the class file actually is stored.

Identifying the class's package

Assuming the class file is found, the code proceeds into Step 0, which is shown below.

 /* * Step 0: If our name contains dots we're in a package so put * that out first. */ x = c.getName(); y = x.substring(0, x.lastIndexOf(".")); if (y.length() > 0) { System.out.println("package "+y+";\n\r"); } 

In this step, the name of the class is retrieved using the getName method in class Class. This method returns the fully qualified name, and if the name contains dots, we can presume that the class was defined as part of a package. So Step 0 is to separate the package name part from the class name part, and print out the package name part on a line that starts with "package...."

Collecting class references from declarations and parameters

With the package statement taken care of, we proceed to Step 1, which is to collect all of the other class names that are referenced by this class. This collection process is shown in the code below. Remember that the three most common places where class names are referenced are as types for fields (instance variables), return types for methods, and as the types of the parameters passed to methods and constructors.

 ff = c.getDeclaredFields(); for (int i = 0; i < ff.length; i++) { x = tName(ff[i].getType().getName(), classRef); } 

In the above code, the array ff is initialized to be an array of Field objects. The loop collects the type name from each field and process it through the tName method. The tName method is a simple helper that returns the shorthand name for a type. So java.lang.String becomes String. And it notes in a hashtable which objects have been seen. At this stage, the code is more interested in collecting class references than in printing.

The next source of class references are the parameters supplied to constructors. The next piece of code, shown below, processes each declared constructor and collects the references from the parameter lists.

 cn = c.getDeclaredConstructors(); for (int i = 0; i  0) { for (int j = 0; j < cx.length; j++) { x = tName(cx[j].getName(), classRef); } } } 

As you can see, I've used the getParameterTypes method in the Constructor class to feed me all of the parameters that a particular constructor takes. These are then processed through the tName method.

An interesting thing to note here is the difference between the method getDeclaredConstructors and the method getConstructors. Both methods return an array of constructors, but the getConstructors method only returns those constructors that are accessible to your class. This is useful if you want to know if you actually can invoke the constructor you've found, but it isn't useful for this application because I want to print out all of the constructors in the class, public or not. The field and method reflectors also have similar versions, one for all members and one only for public members.

아래 표시된 마지막 단계는 모든 방법에서 참조를 수집하는 것입니다. 이 코드는 메소드 유형 (위의 필드와 유사)과 매개 변수 (위의 생성자와 유사)에서 참조를 가져와야합니다.

mm = c.getDeclaredMethods (); for (int i = 0; i 0) {for (int j = 0; j <cx.length; j ++) {x = tName (cx [j] .getName (), classRef); }}}

위의 코드 tName에는 반환 유형을 수집하는 호출 과 각 매개 변수의 유형을 수집하는 호출의 두 가지가 있습니다.