Java Language
Reflectie-API
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 vangetGenericReturnType()
. - 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);
}
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:
- Maak een instantie van klasse met
Class.forName
: deze roept de standaardconstructor aan - Roep
getDeclaredConstructor
van de klasse op door het type parameters door te geven alsClass array
- Nadat u de constructor hebt
newInstance
, maakt unewInstance
door parameterwaarde door te geven alsObject 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.