Zoeken…


Invoering

Reflectie wordt vaak gebruikt door programma's die de mogelijkheid vereisen om het runtimegedrag van toepassingen die in de JVM worden uitgevoerd te onderzoeken of te wijzigen. Java Reflection API wordt gebruikt voor dat doel waar het mogelijk is om klassen, interfaces, velden en methoden tijdens runtime te inspecteren, zonder hun namen te kennen tijdens het compileren. En het maakt het ook mogelijk om nieuwe objecten te instantiëren en methoden aan te roepen met behulp van reflectie.

Opmerkingen

Prestatie

Houd er rekening mee dat reflectie de prestaties kan verminderen, gebruik het alleen wanneer uw taak niet kan worden voltooid zonder reflectie.

Uit de Java-tutorial The Reflection API :

Omdat reflectie typen betreft die dynamisch worden opgelost, kunnen bepaalde optimalisaties van de virtuele Java-machine niet worden uitgevoerd. Bijgevolg hebben reflecterende bewerkingen langzamere prestaties dan hun niet-reflecterende tegenhangers en moeten ze worden vermeden in secties code die vaak worden genoemd in prestatiegevoelige toepassingen.

Invoering

Basics

Met de Reflection-API kan de klassenstructuur van de code tijdens runtime worden gecontroleerd en code dynamisch worden opgeroepen. Dit is zeer krachtig, maar het is ook gevaarlijk omdat de compiler niet statisch kan bepalen of dynamische aanroepen geldig zijn.

Een eenvoudig voorbeeld zou zijn om de openbare constructeurs en methoden van een bepaalde klasse te krijgen:

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

Met deze informatie is het mogelijk om het object te instantiëren en verschillende methoden dynamisch aan te roepen.

Reflectie en generieke typen

Algemene type-informatie is beschikbaar voor:

  • methode parameters, met behulp van getGenericParameterTypes() .
  • methode getGenericReturnType() , met behulp van getGenericReturnType() .
  • openbare velden, met behulp van getGenericType .

Het volgende voorbeeld laat zien hoe de generieke type-informatie in alle drie gevallen kan worden geëxtraheerd:

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

}

Dit resulteert in de volgende uitvoer:

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

Een methode aanroepen

Met behulp van reflectie kan tijdens runtime een methode van een object worden aangeroepen.

Het voorbeeld laat zien hoe de methoden van een String object kunnen worden aangeroepen.

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!"

Velden ophalen en instellen

Met behulp van de Reflection API is het mogelijk om de waarde van een veld tijdens runtime te wijzigen of op te halen. U kunt het bijvoorbeeld in een API gebruiken om verschillende velden op te halen op basis van een factor, zoals het besturingssysteem. U kunt ook modificaties zoals final verwijderen om final modificatievelden toe te staan.

Om dit te doen, moet u de methode Class # getField () gebruiken op een manier zoals hieronder wordt getoond:

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

Velden verkrijgen is veel eenvoudiger. We kunnen Veld # get () en zijn varianten gebruiken om zijn waarde te krijgen:

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

Let op dit:

Wanneer u Class # getDeclaredField gebruikt , gebruikt u dit om een veld in de klasse zelf te krijgen:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Hier wordt HackMe#iAmDeclared als veld verklaard. HackMe#someState is echter geen gedeclareerd veld omdat het is geërfd van zijn superklasse, Hacked.

Oproep aannemer

Het constructorobject verkrijgen

U kunt de Constructor als volgt verkrijgen van het Class object:

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

Waar de constructors één Constructor instantie heeft voor elke openbare constructor die in de klasse wordt gedeclareerd.

Als u de precieze parametertypen kent van de constructor waartoe u toegang wilt, kunt u de specifieke constructor filteren. In het volgende voorbeeld wordt de openbare constructor van de gegeven klasse geretourneerd die een geheel Integer als parameter gebruikt:

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

Als geen constructor overeenkomt met de gegeven constructorargumenten, wordt een NoSuchMethodException gegenereerd.

Nieuwe instantie met Constructor Object

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

De constanten van een opsomming krijgen

Deze opsomming als voorbeeld geven:

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

In Java is een enum-klasse zoals elke andere klasse, maar heeft een aantal gedefinieerde constanten voor de enum-waarden. Bovendien heeft het een veld dat een array is die alle waarden bevat en twee statische methoden met valueOf(String) values() en valueOf(String) .
We kunnen dit zien als we Reflectie gebruiken om alle velden in deze klasse af te drukken

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

de output zal zijn:

NOORDEN
OOSTEN
ZUIDEN
WEST
mate
ENUM $ WAARDEN

Dus we kunnen enum-klassen met Reflectie onderzoeken zoals elke andere klasse. Maar de Reflection API biedt drie opsommingsspecifieke methoden.

enum check

Compass.class.isEnum();

Retourneert true voor klassen die een opsommingstype vertegenwoordigen.

waarden ophalen

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

Retourneert een array van alle enumwaarden zoals Compass.values () maar zonder dat u een instantie nodig hebt.

enum constante controle

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

Toont alle klassenvelden die opsommingswaarden zijn.

Krijg klasse met de (volledig gekwalificeerde) naam

Gegeven een String die de naam van een klasse bevat, is het Class object toegankelijk met Class.forName :

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

Het kan worden opgegeven of de klasse moet worden geïnitialiseerd (tweede parameter van forName ) en welke ClassLoader moet worden gebruikt (derde parameter):

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

Bel overbelaste constructeurs met behulp van reflectie

Voorbeeld: Roep verschillende constructors op door relevante parameters door te geven

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

output:

Default constructor
Constructor :String => StackOverFlow

Uitleg:

  1. Maak een instantie van klasse met Class.forName : deze roept de standaardconstructor aan
  2. Roep getDeclaredConstructor van de klasse op door het type parameters door te geven als Class array
  3. Nadat u de constructor hebt newInstance , maakt u newInstance door parameterwaarde door te geven als Object array

Misbruik van Reflection API om privé- en eindvariabelen te wijzigen

Reflectie is handig wanneer het op de juiste manier voor het juiste doel wordt gebruikt. Door reflectie te gebruiken, hebt u toegang tot privévariabelen en kunt u definitieve variabelen opnieuw initialiseren.

Hieronder staat het codefragment, wat niet wordt aanbevolen.

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

Output:

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

Uitleg:

In normale scenario, private kunt variabelen niet worden geopend buiten de opgegeven klasse (zonder getter en setter methoden). final variabelen kunnen niet opnieuw worden toegewezen na initialisatie.

Reflection doorbreekt beide barrières en kan worden misbruikt om zowel privé- als eindvariabelen te veranderen, zoals hierboven uitgelegd.

field.setAccessible(true) is de sleutel om de gewenste functionaliteit te bereiken.

Constructor van geneste klasse aanroepen

Als u een instantie van een geneste klasse wilt maken, moet u een klasseobject van de omsluitende klasse opgeven als een extra parameter met Class # 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"});
    }
}

Als de geneste klasse statisch is, heeft u deze omsluitende instantie niet nodig.

Dynamische proxy's

Dynamische proxy's hebben niet echt veel te maken met reflectie, maar ze maken deel uit van de API. Het is eigenlijk een manier om een dynamische implementatie van een interface te creëren. Dit kan handig zijn bij het maken van mockup-services.
Een dynamische proxy is een instantie van een interface die is gemaakt met een zogenaamde invocatie-handler die alle methodeaanroepen onderschept en de afhandeling ervan handmatig mogelijk maakt.

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

Het resultaat van deze code is dit:

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

Evil Java hacks met Reflectie

De Reflectie-API kan worden gebruikt om waarden van privé- en eindvelden te wijzigen, zelfs in de standaardbibliotheek van JDK. Dit kan worden gebruikt om het gedrag van enkele bekende klassen te manipuleren, zoals we zullen zien.

Wat niet mogelijk is

Laten we eerst beginnen met de enige beperking betekent het enige veld dat we niet kunnen veranderen met Reflectie. Dat is de Java SecurityManager . Het wordt in java.lang.System verklaard als

private static volatile SecurityManager security = null;

Maar het wordt niet vermeld in de klasse Systeem als we deze code uitvoeren

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

Dat komt door de fieldFilterMap in sun.reflect.Reflection die de kaart zelf en het beveiligingsveld in de System.class en hen tegen elke toegang met Reflection beschermt. We konden de SecurityManager dus niet deactiveren.

Crazy Strings

Elke Java-string wordt door de JVM weergegeven als een instantie van de klasse String . In sommige situaties bespaart de JVM echter heap-ruimte door dezelfde instantie te gebruiken voor Strings die dat zijn. Dit gebeurt voor tekenreeksliteralen en ook voor tekenreeksen die zijn "geïnterneerd" door String.intern() aan te roepen. Dus als u meerdere keren "hello" in uw code heeft, is dit altijd dezelfde objectinstantie.

Strings worden verondersteld onveranderlijk te zijn, maar het is mogelijk om "slechte" reflectie te gebruiken om ze te veranderen. Het onderstaande voorbeeld laat zien hoe we de tekens in een tekenreeks kunnen wijzigen door het value vervangen.

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

Dus deze code zal afdrukken "je stinkt!"

1 = 42

Hetzelfde idee kan worden gebruikt met de Integer Class

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

Alles is waar

En volgens deze stackoverflow-post kunnen we reflectie gebruiken om iets heel ergs te doen.

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

Merk op dat wat we hier doen ervoor zorgen dat de JVM zich op onverklaarbare manieren gedraagt. Dit is erg gevaarlijk.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow