Java Language
Reflection API
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()
, medgetGenericParameterTypes()
. -
getGenericReturnType()
medgetGenericReturnType()
. - 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);
}
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:
- Skapa instans av klass med
Class.forName
: Det kallar standardkonstruktör -
getDeclaredConstructor
för klassen genom att lämna typ av parametrar somClass array
- När du har fått konstruktören, skapa
newInstance
genom attnewInstance
parametervärde somObject 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.