Szukaj…


Wprowadzenie

Odbicie jest powszechnie stosowane w programach, które wymagają zdolności do sprawdzania lub modyfikowania działania środowiska wykonawczego aplikacji działających w JVM. Interfejs API Java Reflection jest wykorzystywany do tego celu, gdy umożliwia sprawdzanie klas, interfejsów, pól i metod w czasie wykonywania, bez znajomości ich nazw w czasie kompilacji. Umożliwia także tworzenie nowych obiektów i wywoływanie metod wykorzystujących odbicie.

Uwagi

Występ

Pamiętaj, że odbicie może obniżyć wydajność, używaj go tylko wtedy, gdy nie można wykonać zadania bez odbicia.

Z samouczka Java The Reflection API :

Ponieważ odbicie obejmuje typy rozwiązywane dynamicznie, nie można przeprowadzić pewnych optymalizacji maszyny wirtualnej Java. W rezultacie operacje odbijające mają mniejszą wydajność niż ich nieodblaskowe odpowiedniki i należy ich unikać w częściach kodu, które są często wywoływane w aplikacjach wrażliwych na wydajność.

Wprowadzenie

Podstawy

Reflection API pozwala sprawdzić strukturę klas kodu w czasie wykonywania i dynamicznie wywoływać kod. Jest to bardzo potężne, ale jest również niebezpieczne, ponieważ kompilator nie jest w stanie statycznie ustalić, czy wywołania dynamiczne są poprawne.

Prostym przykładem byłoby pobranie publicznych konstruktorów i metod danej klasy:

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

Dzięki tym informacjom możliwe jest dynamiczne tworzenie instancji obiektu i wywoływanie różnych metod.

Refleksja i typy ogólne

Ogólne informacje o typie są dostępne dla:

  • parametry metody, przy użyciu getGenericParameterTypes() .
  • metody zwracają typy, używając getGenericReturnType() .
  • pola publiczne , używając getGenericType .

Poniższy przykład pokazuje, jak wyodrębnić ogólne informacje o typie we wszystkich trzech przypadkach:

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;
    }

}

Daje to następujące wyniki:

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

Wywoływanie metody

Za pomocą odbicia można wywołać metodę obiektu w czasie wykonywania.

Przykład pokazuje, jak wywołać metody obiektu 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!"

Pobieranie i ustawianie pól

Za pomocą interfejsu API Reflection można zmienić lub uzyskać wartość pola w czasie wykonywania. Na przykład można użyć go w interfejsie API do pobierania różnych pól w oparciu o czynnik, na przykład system operacyjny. Możesz także usunąć modyfikatory, takie jak final aby umożliwić modyfikowanie pól, które są ostateczne.

Aby to zrobić, musisz użyć metody Class # getField () w sposób taki jak pokazano poniżej:

// 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);

Uzyskiwanie pól jest znacznie łatwiejsze. Możemy użyć pola # get () i jego wariantów, aby uzyskać jego wartość:

// 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);

Uwaga:

Korzystając z klasy # getDeclaredField , użyj jej, aby uzyskać pole w samej klasie:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Tutaj HackMe#iAmDeclared jest zadeklarowanym polem. Jednak HackMe#someState nie jest zadeklarowanym polem, ponieważ jest dziedziczony z nadklasy Hacked.

Wywołanie konstruktora

Zdobywanie obiektu konstruktora

Możesz uzyskać klasę Constructor z obiektu Class następujący sposób:

Class myClass = ... // get a class object
Constructor[] constructors = myClass.getConstructors();

Gdzie zmienna constructors będzie miała jedną instancję Constructor dla każdego publicznego konstruktora zadeklarowanego w klasie.

Jeśli znasz dokładne typy parametrów konstruktora, do którego chcesz uzyskać dostęp, możesz przefiltrować określony konstruktor. Następny przykład zwraca konstruktor publiczny danej klasy, który przyjmuje liczbę Integer jako parametr:

Class myClass = ... // get a class object
Constructor constructor = myClass.getConstructor(new Class[]{Integer.class});

Jeśli żaden konstruktor nie pasuje do podanych argumentów konstruktora, NoSuchMethodException jest NoSuchMethodException .

Nowa instancja za pomocą Konstruktora

Class myClass = MyObj.class // get a class object
Constructor constructor = myClass.getConstructor(Integer.class);
MyObj myObj = (MyObj) constructor.newInstance(Integer.valueOf(123));

Uzyskiwanie stałych wyliczenia

Podanie tego wyliczenia jako przykładu:

enum Compass {
    NORTH(0),
    EAST(90),
    SOUTH(180),
    WEST(270);
    private int degree;
    Compass(int deg){
        degree = deg;
    }
    public int getDegree(){
        return degree;
    }
}

W Javie klasa wyliczeniowa jest jak każda inna klasa, ale ma określone stałe dla wartości wyliczeniowych. Dodatkowo ma pole, które jest tablicą, która przechowuje wszystkie wartości oraz dwie metody statyczne z nazwami values() i valueOf(String) .
Możemy to zobaczyć, jeśli użyjemy Reflection do wydrukowania wszystkich pól w tej klasie

for(Field f : Compass.class.getDeclaredFields())
    System.out.println(f.getName());

wyjście będzie:

PÓŁNOC
WSCHÓD
POŁUDNIE
ZACHÓD
stopień
WARTOŚCI ENUM $

Możemy więc badać klasy wyliczeniowe z Refleksją, jak każda inna klasa. Ale Reflection API oferuje trzy metody specyficzne dla wyliczenia.

sprawdzenie wyliczenia

Compass.class.isEnum();

Zwraca wartość true dla klas reprezentujących typ wyliczeniowy.

pobieranie wartości

Object[] values = Compass.class.getEnumConstants();

Zwraca tablicę wszystkich wartości wyliczeniowych, takich jak Compass.values (), ale bez potrzeby wystąpienia instancji.

stała kontrola wyliczenia

for(Field f : Compass.class.getDeclaredFields()){
    if(f.isEnumConstant())
        System.out.println(f.getName());
}

Wyświetla wszystkie pola klas, które są wartościami wyliczonymi.

Uzyskaj klasę, podając jej (w pełni kwalifikowaną) nazwę

Biorąc pod uwagę String zawierający nazwę klasy, do jej obiektu Class można uzyskać dostęp za pomocą Class.forName :

Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer");
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}
Java SE 1.2

Można określić, czy klasa powinna zostać zainicjowana (drugi parametr forName ) i który ClassLoader powinien zostać użyty (trzeci parametr):

ClassLoader classLoader = ...
boolean initialize = ...
Class clazz = null;
try {
    clazz = Class.forName("java.lang.Integer", initialize, classLoader);
} catch (ClassNotFoundException ex) {
    throw new IllegalStateException(ex);
}

Wywołaj przeciążonych konstruktorów za pomocą odbicia

Przykład: Wywołaj różnych konstruktorów, przekazując odpowiednie parametry

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"});
        
    }
}

wynik:

Default constructor
Constructor :String => StackOverFlow

Wyjaśnienie:

  1. Utwórz instancję klasy za pomocą Class.forName : Wywołuje domyślnego konstruktora
  2. Wywołaj getDeclaredConstructor klasy, przekazując typ parametrów jako Class array
  3. Po uzyskaniu konstruktora utwórz newInstance , przekazując wartość parametru jako Object array

Niewłaściwe użycie interfejsu Reflection API do zmiany zmiennych prywatnych i końcowych

Odbicie jest przydatne, gdy jest właściwie używane we właściwym celu. Za pomocą refleksji można uzyskać dostęp do zmiennych prywatnych i ponownie zainicjować zmienne końcowe.

Poniżej znajduje się fragment kodu, który nie jest zalecany.

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++;
    }
}

Wynik:

A.name=StackOverFlow
A.age=20
A.rep=New Reputation
A.count=25

Wyjaśnienie:

W normalnym scenariuszu nie można uzyskać dostępu do zmiennych private poza zadeklarowaną klasą (bez metod pobierających i ustawiających). zmiennych final nie można przypisać ponownie po inicjalizacji.

Reflection przełamuje obie bariery, które można nadużywać, aby zmienić zarówno zmienne prywatne, jak i końcowe, jak wyjaśniono powyżej.

field.setAccessible(true) jest kluczem do osiągnięcia pożądanej funkcjonalności.

Wywołanie konstruktora klasy zagnieżdżonej

Jeśli chcesz utworzyć instancję wewnętrznej klasy zagnieżdżonej, musisz podać obiekt klasy otaczającej klasy jako dodatkowy parametr w klasie # 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"});
    }
}

Jeśli zagnieżdżona klasa jest statyczna, nie będziesz potrzebować tej otaczającej instancji.

Dynamiczne proxy

Dynamiczne proxy nie mają tak naprawdę wiele wspólnego z Reflection, ale są częścią API. Jest to w zasadzie sposób na stworzenie dynamicznej implementacji interfejsu. Może to być pomocne podczas tworzenia usług makiet.
Dynamiczny serwer proxy to instancja interfejsu utworzona za pomocą tak zwanego modułu obsługi wywołań, który przechwytuje wszystkie wywołania metod i umożliwia ręczne ich wywoływanie.

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();
    }
}

Wynik tego kodu jest następujący:

someMethod1 was invoked!
someMethod2 was invoked!
Parameter: stackoverflow
anotherMethod was invoked!

Złe hacki Java z Reflection

Interfejs API Reflection może być używany do zmiany wartości pól prywatnych i końcowych nawet w domyślnej bibliotece JDK. Można to wykorzystać do manipulowania zachowaniem niektórych dobrze znanych klas, jak zobaczymy.

Co nie jest możliwe

Zacznijmy od pierwszego z jedynym ograniczeniem oznacza jedyne pole, którego nie możemy zmienić za pomocą Reflection. To jest Java SecurityManager . Jest zadeklarowany w java.lang.System jako

private static volatile SecurityManager security = null;

Ale nie będzie wymieniony w klasie System, jeśli uruchomimy ten kod

for(Field f : System.class.getDeclaredFields())
    System.out.println(f);

fieldFilterMap tak z powodu fieldFilterMap w sun.reflect.Reflection który przechowuje samą mapę i pole bezpieczeństwa w System.class i chroni je przed dostępem z Reflection. Dlatego nie mogliśmy dezaktywować SecurityManager .

Crazy Strings

Każdy ciąg Java jest reprezentowany przez JVM jako instancja klasy String . Jednak w niektórych sytuacjach JVM oszczędza miejsce na sterty, używając tej samej instancji dla ciągów. Dzieje się tak w przypadku literałów łańcuchowych, a także łańcuchów, które zostały „internowane” przez wywołanie String.intern() . Jeśli więc w kodzie znajduje się "hello" jest to zawsze ta sama instancja obiektu.

Ciągi mają być niezmienne, ale można je zmienić, używając „złego” odbicia. Poniższy przykład pokazuje, jak możemy zmienić znaki w ciągu, zastępując jego pole 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");
    }
}

Więc ten kod wyświetli „śmierdzisz!”

1 = 42

Ten sam pomysł można zastosować z klasą całkowitą

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));
    }
}

Wszystko jest prawdą

I zgodnie z tym postem przepełnienia stosu możemy użyć refleksji, aby zrobić coś naprawdę złego.

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);
    }
}

Zauważ, że to, co tutaj robimy, spowoduje, że JVM będzie zachowywać się w niewytłumaczalny sposób. To jest bardzo niebezpieczne.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow