Java Language
리플렉션 API
수색…
소개
리플렉션은 일반적으로 JVM에서 실행되는 응용 프로그램의 런타임 동작을 검사하거나 수정해야하는 프로그램에서 사용됩니다. Java Reflection API 는 런타임시 클래스, 인터페이스, 필드 및 메소드를 검사 할 수 있도록 컴파일 타임에 이름을 모르는 경우에 사용됩니다. 또한 새로운 객체를 인스턴스화하고 리플렉션을 사용하여 메소드를 호출 할 수 있습니다.
비고
공연
리플렉션을 사용하면 성능이 저하 될 수 있으므로 리플렉션없이 작업을 완료 할 수없는 경우에만 리플렉션을 사용하십시오.
Java 자습서 에서 Reflection API :
리플렉션에는 동적으로 해석되는 유형이 포함되기 때문에 특정 Java 가상 시스템 최적화를 수행 할 수 없습니다. 따라서 반사 작업은 비 반사 작업보다 성능이 떨어지며 성능에 민감한 응용 프로그램에서 자주 호출되는 코드 섹션에서는 사용하지 않아야합니다.
소개
기초
리플렉션 API를 사용하면 런타임에 코드의 클래스 구조를 확인하고 코드를 동적으로 호출 할 수 있습니다. 이것은 매우 강력하지만 컴파일러가 동적 호출이 유효한지 정적으로 결정할 수 없으므로 위험합니다.
간단한 예제는 주어진 클래스의 public 생성자와 메소드를 얻는 것입니다 :
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// This is a object representing the String class (not an instance of String!)
Class<String> clazz = String.class;
Constructor<?>[] constructors = clazz.getConstructors(); // returns all public constructors of String
Method[] methods = clazz.getMethods(); // returns all public methods from String and parents
이 정보를 사용하여 객체를 인스턴스화하고 다른 메소드를 동적으로 호출 할 수 있습니다.
반사 및 일반 유형
일반 유형 정보는 다음에서 사용할 수 있습니다.
- 메소드 매개 변수에
getGenericParameterTypes()
합니다. -
getGenericReturnType()
사용하여 메소드 리턴 유형을 리턴합니다. -
getGenericType
사용하여 공용 필드.
다음 예제는 세 가지 경우 모두 제네릭 형식 정보를 추출하는 방법을 보여줍니다.
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class GenericTest {
public static void main(final String[] args) throws Exception {
final Method method = GenericTest.class.getMethod("testMethod", Map.class);
final Field field = GenericTest.class.getField("testField");
System.out.println("Method parameter:");
final Type parameterType = method.getGenericParameterTypes()[0];
displayGenericType(parameterType, "\t");
System.out.println("Method return type:");
final Type returnType = method.getGenericReturnType();
displayGenericType(returnType, "\t");
System.out.println("Field type:");
final Type fieldType = field.getGenericType();
displayGenericType(fieldType, "\t");
}
private static void displayGenericType(final Type type, final String prefix) {
System.out.println(prefix + type.getTypeName());
if (type instanceof ParameterizedType) {
for (final Type subtype : ((ParameterizedType) type).getActualTypeArguments()) {
displayGenericType(subtype, prefix + "\t");
}
}
}
public Map<String, Map<Integer, List<String>>> testField;
public List<Number> testMethod(final Map<String, Double> arg) {
return null;
}
}
결과는 다음과 같습니다.
Method parameter:
java.util.Map<java.lang.String, java.lang.Double>
java.lang.String
java.lang.Double
Method return type:
java.util.List<java.lang.Number>
java.lang.Number
Field type:
java.util.Map<java.lang.String, java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>>
java.lang.String
java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>
java.lang.Integer
java.util.List<java.lang.String>
java.lang.String
메소드 호출하기
리플렉션을 사용하면 런타임 중에 객체의 메서드를 호출 할 수 있습니다.
이 예제는 String
객체의 메소드를 호출하는 방법을 보여줍니다.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
String s = "Hello World!";
// method without parameters
// invoke s.length()
Method method1 = String.class.getMethod("length");
int length = (int) method1.invoke(s); // variable length contains "12"
// method with parameters
// invoke s.substring(6)
Method method2 = String.class.getMethod("substring", int.class);
String substring = (String) method2.invoke(s, 6); // variable substring contains "World!"
필드 가져 오기 및 설정
리플렉션 API를 사용하면 런타임에 필드 값을 변경하거나 가져올 수 있습니다. 예를 들어, API에서이를 사용하여 OS와 같은 요소를 기반으로 다른 필드를 검색 할 수 있습니다. final
과 같은 수정자를 제거하여 final
필드를 수정할 수도 있습니다.
이렇게하려면 아래 표시된 것과 같이 Class # getField () 메서드를 사용해야합니다.
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Get the field in class Field "modifiers". Note that it does not
// need to be static
Field modifiersField = Field.class.getDeclaredField("modifiers");
// Allow access from anyone even if it's declared private
modifiersField.setAccessible(true);
// Get the modifiers on the "NAME" field as an int.
int existingModifiersOnNameField = nameField.getModifiers();
// Bitwise AND NOT Modifier.FINAL (16) on the existing modifiers
// Readup here https://en.wikipedia.org/wiki/Bitwise_operations_in_C
// if you're unsure what bitwise operations are.
int newModifiersOnNameField = existingModifiersOnNameField & ~Modifier.FINAL;
// Set the value of the modifiers field under an object for non-static fields
modifiersField.setInt(nameField, newModifiersOnNameField);
// Set it to be accessible. This overrides normal Java
// private/protected/package/etc access control checks.
nameField.setAccessible(true);
// Set the value of "NAME" here. Note the null argument.
// Pass null when modifying static fields, as there is no instance object
nameField.set(null, "Hacked by reflection...");
// Here I can directly access it. If needed, use reflection to get it. (Below)
System.out.println(SomeClass.NAME);
필드를 가져 오는 것이 훨씬 쉽습니다. Field # get () 및 그 변형을 사용하여 값을 얻을 수 있습니다.
// Get the field in class SomeClass "NAME".
Field nameField = SomeClass.class.getDeclaredField("NAME");
// Set accessible for private fields
nameField.setAccessible(true);
// Pass null as there is no instance, remember?
String name = (String) nameField.get(null);
참고 사항 :
Class # getDeclaredField 를 사용할 때 클래스를 사용하여 클래스 자체의 필드를 가져옵니다.
class HackMe extends Hacked {
public String iAmDeclared;
}
class Hacked {
public String someState;
}
여기서 HackMe#iAmDeclared
는 선언 된 필드입니다. 그러나 HackMe#someState
는 수퍼 클래스 인 Hacked에서 상속받은 선언 된 필드가 아닙니다.
호출 생성자
생성자 객체 얻기
다음과 같이 Class
객체에서 Constructor
클래스를 얻을 수 있습니다.
Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();
constructors
변수에는 클래스에 선언 된 각 public 생성자에 대해 하나의 Constructor
인스턴스가 있습니다.
액세스하려는 생성자의 정확한 매개 변수 유형을 알고있는 경우 특정 생성자를 필터링 할 수 있습니다. 다음의 예에서는, Integer
를 파라미터로서 취하는, 지정된 클래스의 public 생성자를 돌려줍니다.
Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});
지정된 생성자 인수와 일치하는 생성자가 없으면 NoSuchMethodException
이 발생합니다.
생성자 객체를 사용하는 새로운 인스턴스
Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));
열거 형 상수 가져 오기
이 열거 형을 예제로 제공 :
enum Compass {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270);
private int degree;
Compass(int deg){
degree = deg;
}
public int getDegree(){
return degree;
}
}
Java에서 열거 형 클래스는 다른 클래스와 비슷하지만 열거 형 값에 대해 몇 가지 한정 상수가 있습니다. 또한 모든 값을 보유하는 배열 인 필드와 이름 values()
및 valueOf(String)
두 개의 정적 메서드가 있습니다.
Reflection을 사용하면이 클래스의 모든 필드를 인쇄 할 수 있습니다.
for(Field f : Compass.class.getDeclaredFields())
System.out.println(f.getName());
출력은 다음과 같습니다.
북쪽
동쪽
남쪽
서쪽
정도
ENUM $ VALUES
그래서 다른 클래스와 마찬가지로 Reflection으로 enum 클래스를 검사 할 수 있습니다. 그러나 Reflection API는 세 가지 열거 형 메서드를 제공합니다.
열거 형 체크
Compass.class.isEnum();
enum 형을 나타내는 클래스에 대해서 true를 돌려줍니다.
값 검색하기
Object[] values = Compass.class.getEnumConstants();
Compass.values ()와 같은 모든 enum 값의 배열을 반환하지만 인스턴스가 필요하지 않습니다.
enum constant check
for(Field f : Compass.class.getDeclaredFields()){
if(f.isEnumConstant())
System.out.println(f.getName());
}
enum 값인 모든 클래스 필드를 나열합니다.
(완전한) 이름으로 클래스 가져 오기
클래스의 이름을 포함하는 String
이 주어지면 Class
객체는 Class.forName
사용하여 액세스 할 수 있습니다.
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
클래스를 초기화해야하는 경우 ( forName
두 번째 매개 변수) 및 사용할 ClassLoader
(세 번째 매개 변수)를 지정할 수 있습니다.
ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(ex);
}
리플렉션을 사용하여 오버로드 된 생성자 호출
예 : 관련 매개 변수를 전달하여 다른 생성자 호출
import java.lang.reflect.*;
class NewInstanceWithReflection{
public NewInstanceWithReflection(){
System.out.println("Default constructor");
}
public NewInstanceWithReflection( String a){
System.out.println("Constructor :String => "+a);
}
public static void main(String args[]) throws Exception {
NewInstanceWithReflection object = (NewInstanceWithReflection)Class.forName("NewInstanceWithReflection").newInstance();
Constructor constructor = NewInstanceWithReflection.class.getDeclaredConstructor( new Class[] {String.class});
NewInstanceWithReflection object1 = (NewInstanceWithReflection)constructor.newInstance(new Object[]{"StackOverFlow"});
}
}
산출:
Default constructor
Constructor :String => StackOverFlow
설명:
-
Class.forName
사용하여 클래스 인스턴스 만들기 : 기본 생성자를 호출합니다. -
Class array
형으로서 파라미터의 형태를 건네주는 것으로, 클래스의getDeclaredConstructor
를 호출합니다. - 생성자를
newInstance
후 매개 변수 값을Object array
로 전달하여newInstance
를 만듭니다.
개인 및 최종 변수를 변경하는 Reflection API의 잘못된 사용
반사는 올바른 목적으로 적절하게 사용될 때 유용합니다. 리플렉션을 사용하여 개인 변수에 액세스하고 최종 변수를 다시 초기화 할 수 있습니다.
다음은 권장 되지 않는 코드 스 니펫입니다.
import java.lang.reflect.*;
public class ReflectionDemo{
public static void main(String args[]){
try{
Field[] fields = A.class.getDeclaredFields();
A a = new A();
for ( Field field:fields ) {
if(field.getName().equalsIgnoreCase("name")){
field.setAccessible(true);
field.set(a, "StackOverFlow");
System.out.println("A.name="+field.get(a));
}
if(field.getName().equalsIgnoreCase("age")){
field.set(a, 20);
System.out.println("A.age="+field.get(a));
}
if(field.getName().equalsIgnoreCase("rep")){
field.setAccessible(true);
field.set(a,"New Reputation");
System.out.println("A.rep="+field.get(a));
}
if(field.getName().equalsIgnoreCase("count")){
field.set(a,25);
System.out.println("A.count="+field.get(a));
}
}
}catch(Exception err){
err.printStackTrace();
}
}
}
class A {
private String name;
public int age;
public final String rep;
public static int count=0;
public A(){
name = "Unset";
age = 0;
rep = "Reputation";
count++;
}
}
산출:
A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25
설명:
일반적인 시나리오에서 private
변수는 getter 및 setter 메서드없이 선언 된 클래스 외부에서 액세스 할 수 없습니다. final
변수는 초기화 후에 다시 할당 될 수 없습니다.
Reflection
는 두 장벽을 허물어 위에서 설명한대로 개인 변수와 최종 변수 모두를 변경하는 것을 학대 할 수 있습니다.
field.setAccessible(true)
는 원하는 기능을 달성하는 데 필요한 키입니다.
중첩 클래스의 생성자 호출
내부 중첩 클래스의 인스턴스를 만들려면 클래스 # getDeclaredConstructor를 사용하여 추가 클래스로 클래스의 클래스 객체를 제공해야합니다.
public class Enclosing{
public class Nested{
public Nested(String a){
System.out.println("Constructor :String => "+a);
}
}
public static void main(String args[]) throws Exception {
Class<?> clazzEnclosing = Class.forName("Enclosing");
Class<?> clazzNested = Class.forName("Enclosing$Nested");
Enclosing objEnclosing = (Enclosing)clazzEnclosing.newInstance();
Constructor<?> constructor = clazzNested.getDeclaredConstructor(new Class[]{Enclosing.class, String.class});
Nested objInner = (Nested)constructor.newInstance(new Object[]{objEnclosing, "StackOverFlow"});
}
}
중첩 클래스가 정적 인 경우이 인 클로징 인스턴스가 필요하지 않습니다.
동적 프록시
동적 프록시는 실제로 리플렉션과 관련이 없지만 API의 일부입니다. 이는 기본적으로 인터페이스의 동적 구현을 만드는 방법입니다. 이것은 모형 서비스를 만들 때 도움이 될 수 있습니다.
동적 프록시는 모든 메소드 호출을 인터셉트하고 호출을 수동으로 처리 할 수 있도록하는 호출 핸들러로 작성된 인터페이스의 인스턴스입니다.
public class DynamicProxyTest {
public interface MyInterface1{
public void someMethod1();
public int someMethod2(String s);
}
public interface MyInterface2{
public void anotherMethod();
}
public static void main(String args[]) throws Exception {
// the dynamic proxy class
Class<?> proxyClass = Proxy.getProxyClass(
ClassLoader.getSystemClassLoader(),
new Class[] {MyInterface1.class, MyInterface2.class});
// the dynamic proxy class constructor
Constructor<?> proxyConstructor =
proxyClass.getConstructor(InvocationHandler.class);
// the invocation handler
InvocationHandler handler = new InvocationHandler(){
// this method is invoked for every proxy method call
// method is the invoked method, args holds the method parameters
// it must return the method result
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if(methodName.equals("someMethod1")){
System.out.println("someMethod1 was invoked!");
return null;
}
if(methodName.equals("someMethod2")){
System.out.println("someMethod2 was invoked!");
System.out.println("Parameter: " + args[0]);
return 42;
}
if(methodName.equals("anotherMethod")){
System.out.println("anotherMethod was invoked!");
return null;
}
System.out.println("Unkown method!");
return null;
}
};
// create the dynamic proxy instances
MyInterface1 i1 = (MyInterface1) proxyConstructor.newInstance(handler);
MyInterface2 i2 = (MyInterface2) proxyConstructor.newInstance(handler);
// and invoke some methods
i1.someMethod1();
i1.someMethod2("stackoverflow");
i2.anotherMethod();
}
}
이 코드의 결과는 다음과 같습니다.
someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!
악의적 인 자바 해킹 반사와 함께
Reflection API는 JDK 기본 라이브러리에서도 개인 필드와 최종 필드의 값을 변경하는 데 사용될 수 있습니다. 이것은 우리가 보게 될 것처럼 잘 알려진 클래스의 행동을 조작하는 데 사용될 수 있습니다.
가능하지 않은 것
유일한 제한을 가지고 처음부터 시작할 수 있습니다. 우리는 Reflection으로 변경할 수없는 유일한 필드를 의미합니다. 그것이 Java SecurityManager
입니다. java.lang.System 로서 다음과 같이 선언됩니다.
private static volatile SecurityManager security = null;
하지만이 코드를 실행하면 System 클래스에 나열되지 않습니다.
for(Field f : System.class.getDeclaredFields())
System.out.println(f);
System.class
의 맵 자체와 보안 필드를 보유하고있는 sun.reflect.Reflection
의 fieldFilterMap
으로 sun.reflect.Reflection
Reflection을 통한 액세스로부터 보호합니다. 따라서 SecurityManager
비활성화 할 수 없습니다.
미친 끈
각 Java String은 JVM에 의해 String
클래스의 인스턴스로 나타납니다. 그러나 경우에 따라 JVM은 동일한 인스턴스를 사용하여 힙 공간을 절약합니다. 이것은 문자열 리터럴과 String.intern()
을 호출하여 "interned"된 문자열에 대해서도 발생합니다. 따라서 코드에 여러 번 "hello"
가 있으면 항상 동일한 객체 인스턴스입니다.
문자열은 변경할 수 없지만 "악"반사를 사용하여 문자열을 변경할 수 있습니다. 아래 예제는 value
필드를 바꾸어 문자열의 문자를 변경하는 방법을 보여줍니다.
public class CrazyStrings {
static {
try {
Field f = String.class.getDeclaredField("value");
f.setAccessible(true);
f.set("hello", "you stink!".toCharArray());
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println("hello");
}
}
그래서이 코드는 "당신 냄새!"라고 인쇄 할 것입니다.
1 = 42
같은 생각을 정수 클래스와 함께 사용할 수 있습니다.
public class CrazyMath {
static {
try {
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.setInt(Integer.valueOf(1), 42);
} catch (Exception e) {
}
}
public static void main(String args[]) {
System.out.println(Integer.valueOf(1));
}
}
모든 것이 사실이다.
그리고이 stackoverflow 게시물 에 따라 우리는 반사를 사용하여 정말 나쁜 것을 할 수 있습니다.
public class Evil {
static {
try {
Field field = Boolean.class.getField("FALSE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, true);
} catch (Exception e) {
}
}
public static void main(String args[]){
System.out.format("Everything is %s", false);
}
}
여기에서 우리가하는 일은 JVM이 불가해의 방식으로 행동하게 할 것이라는 점에 유의하십시오. 이것은 매우 위험합니다.