Sök…


Introduktion

Reflektion används vanligtvis av program som kräver förmågan att undersöka eller ändra runtime-beteendet för applikationer som körs i JVM. Java Reflection API används för det ändamålet där det gör det möjligt att inspektera klasser, gränssnitt, fält och metoder vid körning, utan att veta deras namn vid sammanställningstiden. Och det gör det också möjligt att instansera nya objekt och åberopa metoder med reflektion.

Anmärkningar

Prestanda

Tänk på att reflektion kan minska prestandan, använd den bara när din uppgift inte kan slutföras utan reflektion.

Från Java-självstudien The Reflection API :

Eftersom reflektion involverar typer som är dynamiskt löst kan vissa virtuella Java-optimeringsmaskiner inte utföras. Följaktligen har reflekterande operationer långsammare prestanda än deras icke-reflekterande motsvarigheter och bör undvikas i kodavsnitt som ofta kallas i prestandakänsliga applikationer.

Introduktion

Grunderna

Reflektions-API: n gör det möjligt att kontrollera kodens klassstruktur vid körning och anropa kod dynamiskt. Detta är mycket kraftfullt, men det är också farligt eftersom kompilatorn inte kan statiskt bestämma om dynamiska invokationer är giltiga.

Ett enkelt exempel skulle vara att få de offentliga konstruktörerna och metoderna i en viss klass:

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

Med denna information är det möjligt att inställa objektet och kalla olika metoder dynamiskt.

Reflektion och generiska typer

Generisk typinformation är tillgänglig för:

  • getGenericParameterTypes() , med getGenericParameterTypes() .
  • getGenericReturnType() med getGenericReturnType() .
  • offentliga fält med getGenericType .

Följande exempel visar hur man extraherar information om generisk typ i alla tre fallen:

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

}

Detta resulterar i följande utgång:

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

Åkalla en metod

Med hjälp av reflektion kan en metod för ett objekt åberopas under körning.

Exemplet visar hur man anropar metoderna för ett 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!"

Få och ställa in fält

Med Reflection API är det möjligt att ändra eller få värdet på ett fält vid körning. Du kan till exempel använda den i ett API för att hämta olika fält baserade på en faktor, som OS. Du kan också ta bort modifierare som final att tillåta modifieringsfält som är slutgiltiga.

För att göra det måste du använda metoden Class # getField () på ett sätt som det som visas nedan:

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

Att få fält är mycket enklare. Vi kan använda Fält # get () och dess varianter för att få dess värde:

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

Observera detta:

När du använder klass # getDeclaredField använder du det för att få ett fält i klassen:

class HackMe extends Hacked {
    public String iAmDeclared;
}

class Hacked {
    public String someState;
}

Här HackMe#iAmDeclared som fält. HackMe#someState är dock inte ett deklarerat fält eftersom det ärvs från dess superklass, Hacked.

Ring konstruktör

Hämta konstruktörsobjektet

Du kan få Constructor klass från Class objektet så här:

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

Där constructors kommer att ha en Constructor instans för varje offentlig konstruktör som deklareras i klassen.

Om du känner till de exakta parametertyperna för konstruktören du vill komma åt kan du filtrera den specifika konstruktören. Nästa exempel returnerar den offentliga konstruktören för den givna klassen som tar ett Integer som parameter:

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

Om ingen konstruktör matchar de givna konstruktörargumenten kastas en NoSuchMethodException .

Ny instans med konstruktörsobjekt

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

Få konstant av en uppräkning

Ge denna uppräkning som exempel:

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

I Java är enum-klass som alla andra klass men har vissa definierade konstanter för enum-värdena. Dessutom har det ett fält som är en matris som innehåller alla värden och två statiska metoder med values() och valueOf(String) .
Vi kan se detta om vi använder Reflektion för att skriva ut alla fält i den här klassen

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

utgången kommer att vara:

NORR
ÖST
SÖDER
VÄSTRA
grad
ENUM $ VÄRDEN

Så vi kunde undersöka enumklasser med reflektion som alla andra klasser. Men Reflection API erbjuder tre enumspecifika metoder.

enum check

Compass.class.isEnum();

Returnerar sant för klasser som representerar en enumtyp.

hämta värden

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

Returnerar en matris med alla enumvärden som Compass.values () men utan behov av en instans.

enum konstant kontroll

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

Listar alla klassfält som är enumvärden.

Få klass med sitt (fullt kvalificerade) namn

Med tanke på en String innehåller namnet på en Class kan man Class.forName åtkomst till Class objektet med Class.forName :

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

Det kan specificeras om klassen ska initieras (andra parameter för forName ) och vilken ClassLoader ska användas (tredje parameter):

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

Ring överbelastade konstruktörer med reflektion

Exempel: Åkalla olika konstruktörer genom att lämna relevanta parametrar

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

produktion:

Default constructor
Constructor :String => StackOverFlow

Förklaring:

  1. Skapa instans av klass med Class.forName : Det kallar standardkonstruktör
  2. getDeclaredConstructor för klassen genom att lämna typ av parametrar som Class array
  3. När du har fått konstruktören, skapa newInstance genom att newInstance parametervärde som Object array

Missbruk av Reflection API för att ändra privata och slutliga variabler

Reflektion är användbart när den används på rätt sätt. Genom att använda reflektion kan du få åtkomst till privata variabler och initialisera slutliga variabler.

Nedan visas kodavsnittet, som inte rekommenderas.

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

Produktion:

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

Förklaring:

I normalt scenario kan inte private variabler nås utanför den deklarerade klass (utan getter och setter-metoder). final variabler kan inte tilldelas efter initialisering.

Reflection bryter båda barriärerna kan missbrukas för att ändra både privata och slutliga variabler som förklarats ovan.

field.setAccessible(true) är nyckeln för att uppnå önskad funktionalitet.

Ring konstruktör av kapslad klass

Om du vill skapa en instans av en inre kapslad klass måste du tillhandahålla ett klassobjekt i den bifogade klassen som en extra parameter med klass # 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"});
    }
}

Om den kapslade klassen är statisk behöver du inte den här omslutande instansen.

Dynamiska fullmakter

Dynamic Proxies har inte riktigt mycket att göra med Reflektion men de är en del av API. Det är i princip ett sätt att skapa en dynamisk implementering av ett gränssnitt. Detta kan vara till hjälp när du skapar mockup-tjänster.
En dynamisk proxy är ett exempel på ett gränssnitt som skapas med en så kallad invokationshanterare som avbryter alla metodsamtal och gör det möjligt att hantera deras invokation manuellt.

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

Resultatet av denna kod är detta:

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

Onda Java-hackar med reflektion

Reflection API kan användas för att ändra värden på privata och slutliga fält även i JDKs standardbibliotek. Detta kan användas för att manipulera beteendet hos vissa välkända klasser som vi kommer att se.

Vad som inte är möjligt

Låt oss börja först med den enda begränsningen betyder det enda fältet vi inte kan ändra med Reflektion. Det är Java SecurityManager . Det förklaras i java.lang.System som

private static volatile SecurityManager security = null;

Men det kommer inte att listas i systemklassen om vi kör denna kod

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

Det är på grund av fieldFilterMap i sun.reflect.Reflection som innehåller kartan själv och säkerhetsfältet i System.class och skyddar dem mot alla åtkomst med Reflektion. Så vi kunde inte inaktivera SecurityManager .

Crazy Strings

Varje Java String representeras av JVM som ett exempel på String klassen. I vissa situationer sparar JVM dock högutrymme genom att använda samma instans för strängar som är. Detta händer för strängbokstäver, och även för strängar som har "internerats" genom att ringa String.intern() . Så om du har "hello" i din kod flera gånger är det alltid samma objektinstans.

Strängar är tänkta att vara oföränderliga, men det är möjligt att använda "onda" reflektion för att förändra dem. Exemplet nedan visar hur vi kan ändra tecken i en sträng genom att ersätta dess 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");
    }
}

Så den här koden kommer att skriva ut "du stinker!"

1 = 42

Samma idé kan användas med heltalsklassen

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

Allt är sant

Och enligt detta stackoverflow-inlägg kan vi använda reflektion för att göra något riktigt ont.

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

Observera att det vi gör här kommer att få JVM att bete sig på oförklarliga sätt. Detta är mycket farligt.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow