수색…


소개

리플렉션은 일반적으로 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);
}
Java SE 1.2

클래스를 초기화해야하는 경우 ( 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

설명:

  1. Class.forName 사용하여 클래스 인스턴스 만들기 : 기본 생성자를 호출합니다.
  2. Class array 형으로서 파라미터의 형태를 건네주는 것으로, 클래스의 getDeclaredConstructor 를 호출합니다.
  3. 생성자를 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.ReflectionfieldFilterMap 으로 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이 불가해의 방식으로 행동하게 할 것이라는 점에 유의하십시오. 이것은 매우 위험합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow