Java Reflection biedt de mogelijkheid om het runtime-gedrag van een applicatie te inspecteren en aan te passen. Reflectie in Java is een van de geavanceerde onderwerpen van core Java. Met behulp van Java-reflectie kunnen we een klasse, interface, enum inspecteren, hun structuur, methoden en veldinformatie opvragen tijdens runtime, zelfs als de klasse niet toegankelijk is tijdens compileertijd. We kunnen ook reflectie gebruiken om een object te instantiëren, de methoden ervan aan te roepen en veldwaarden te wijzigen.
Java Reflection
- Reflectie in Java
- Java Reflectie voor Klassen
- Krijg Klasse Object
- Krijg Superklasse
- Krijg Openbare Lidklassen
- Krijg Gedeclareerde Klassen
- Krijg Verklarende Klasse
- Krijg Pakketnaam
- Krijg Klasse Modifiers
- Krijg Type Parameters
- Krijg Geïmplementeerde Interfaces
- Krijg Alle Openbare Methoden
- Krijg Alle Openbare Constructeurs
- Krijg Alle Openbare Velden
- Krijg Alle Annotations
- Java-reflectie voor velden
- Java-reflectie voor methoden
- Java-reflectie voor constructors
- Java Reflectie voor Aantekeningen
Reflectie in Java is een zeer krachtig concept en het is van weinig nut in normaal programmeren, maar het is de ruggengraat voor de meeste Java, J2EE-frameworks. Enkele van de frameworks die Java-reflectie gebruiken zijn:
- JUnit – gebruikt reflectie om de @Test-aantekening te ontleden om de testmethoden te krijgen en deze vervolgens aan te roepen.
- Spring – afhankelijkheidsinjectie, lees meer op Spring Afhankelijkheidsinjectie
- Tomcat webcontainer om het verzoek naar het juiste module door te sturen door hun web.xml-bestanden en verzoek-URI te ontleden.
- Eclipse automatisch aanvullen van methodenamen
- Struts
- Hibernate
De lijst is eindeloos en ze maken allemaal gebruik van java reflectie omdat al deze frameworks geen kennis hebben en geen toegang hebben tot door de gebruiker gedefinieerde klassen, interfaces, hun methoden, enz. We moeten geen reflectie gebruiken in normaal programmeren waar we al toegang hebben tot de klassen en interfaces vanwege de volgende nadelen.
- Slechte prestaties – Omdat java reflectie de types dynamisch oplost, omvat het verwerkingsstappen zoals het scannen van het klassenpad om de klasse te vinden om te laden, wat leidt tot trage prestaties.
- Beveiligingsbeperkingen – Reflectie vereist uitvoeringsmachtigingen die mogelijk niet beschikbaar zijn voor een systeem dat wordt uitgevoerd onder een beveiligingsbeheerder. Dit kan ertoe leiden dat uw toepassing tijdens runtime mislukt vanwege de beveiligingsbeheerder.
- Beveiligingsproblemen – Met reflectie kunnen we delen van code benaderen waar we geen toegang toe zouden moeten hebben, bijvoorbeeld kunnen we private velden van een klasse benaderen en de waarde ervan wijzigen. Dit kan een ernstige bedreiging voor de beveiliging vormen en ervoor zorgen dat uw toepassing zich abnormaal gedraagt.
- Hoge onderhoudskosten – Reflectiecode is moeilijk te begrijpen en te debuggen, ook kunnen eventuele problemen met de code niet worden gevonden op compileertijd omdat de klassen mogelijk niet beschikbaar zijn, waardoor het minder flexibel en moeilijk te onderhouden is.
In Java is elk object ofwel een primitief type of een referentie. Alle klassen, enumeraties en arrays zijn referentietypes en erven van java.lang.Object
. Primitieve typen zijn – boolean, byte, short, int, long, char, float en double. java.lang.Class is het toegangspunt voor alle reflectiebewerkingen. Voor elk objecttype maakt JVM een onveranderlijk exemplaar van java.lang.Class
aan dat methoden biedt om de runtime-eigenschappen van het object te onderzoeken en nieuwe objecten te maken, de methoden ervan aan te roepen en objectvelden in te stellen/ophalen. In dit gedeelte zullen we kijken naar belangrijke methoden van Class. Voor het gemak maak ik enkele klassen en interfaces met erfhechtingshiërarchie.
package com.journaldev.reflection;
public interface BaseInterface {
public int interfaceInt=0;
void method1();
int method2(String str);
}
package com.journaldev.reflection;
public class BaseClass {
public int baseInt;
private static void method3(){
System.out.println("Method3");
}
public int method4(){
System.out.println("Method4");
return 0;
}
public static int method5(){
System.out.println("Method5");
return 0;
}
void method6(){
System.out.println("Method6");
}
// inner public class
public class BaseClassInnerClass{}
//member public enum
public enum BaseClassMemberEnum{}
}
package com.journaldev.reflection;
@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {
public int publicInt;
private String privateString="private string";
protected boolean protectedBoolean;
Object defaultObject;
public ConcreteClass(int i){
this.publicInt=i;
}
@Override
public void method1() {
System.out.println("Method1 impl.");
}
@Override
public int method2(String str) {
System.out.println("Method2 impl.");
return 0;
}
@Override
public int method4(){
System.out.println("Method4 overriden.");
return 0;
}
public int method5(int i){
System.out.println("Method4 overriden.");
return 0;
}
// inner classes
public class ConcreteClassPublicClass{}
private class ConcreteClassPrivateClass{}
protected class ConcreteClassProtectedClass{}
class ConcreteClassDefaultClass{}
//member enum
enum ConcreteClassDefaultEnum{}
public enum ConcreteClassPublicEnum{}
//member interface
public interface ConcreteClassPublicInterface{}
}
Laten we eens kijken naar enkele belangrijke reflectiemethoden voor klassen.
Krijg Klasse Object
We kunnen de Klasse van een object krijgen met behulp van drie methoden – via de statische variabele class
, door de getClass()
-methode van het object te gebruiken en java.lang.Class.forName(String fullyClassifiedClassName)
. Voor primitieve typen en arrays kunnen we de statische variabele class
gebruiken. Wrapperklassen bieden een andere statische variabele TYPE
om de klasse te krijgen.
// Krijg Klasse met reflectie
Class > concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
// onderstaande methode wordt het meest gebruikt in frameworks zoals JUnit
//Spring dependency injection, Tomcat webcontainer
//Eclipse automatische aanvulling van methodenamen, hibernate, Struts2 etc.
//omdat ConcreteClass niet beschikbaar is op compileertijd
concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass
//voor primitieve typen, wrapperklassen en arrays
Class > booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean
Class > cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double
Class > cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]
Class > twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]
getCanonicalName()
geeft de canonieke naam van de onderliggende klasse terug. Let op dat java.lang.Class gebruik maakt van Generics, het helpt frameworks ervoor te zorgen dat de opgehaalde klasse een subklasse is van het framework Base Class. Bekijk Java Generics Tutorial om meer te leren over generics en de wildcards.
Krijg Superklasse
getSuperclass() methode op een Class object geeft de superklasse van de klasse terug. Als deze klasse de Object klasse, een interface, een primitief type, of void vertegenwoordigt, wordt er null geretourneerd. Als dit object een arrayklasse vertegenwoordigt, wordt het Class object dat de Object klasse vertegenwoordigt geretourneerd.
Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"
Krijg Openbare Ledenklassen
De methode getClasses()
van een Klasse-representatie van een object retourneert een array met Class-objecten die alle openbare klassen, interfaces en enums vertegenwoordigen die lid zijn van de klasse die wordt vertegenwoordigd door dit Class-object. Dit omvat openbare klassen en interfacemembers die zijn geërfd van superklassen en openbare klassen en interfacemembers die zijn gedeclareerd door de klasse. Deze methode retourneert een array van lengte 0 als dit Class-object geen openbare lidklassen of interfaces heeft, of als dit Class-object een primitief type, een arrayklasse of void vertegenwoordigt.
Class >[] classes = concreteClass.getClasses();
//[klasse com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//klasse com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//klasse com.journaldev.reflection.BaseClass$BaseClassInnerClass,
//klasse com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));
Krijg gedeclareerde klassen
De methode getDeclaredClasses()
retourneert een array van Class-objecten die alle klassen en interfaces weerspiegelen die zijn gedeclareerd als leden van de klasse die wordt vertegenwoordigd door dit Class-object. De geretourneerde array bevat geen klassen die zijn gedeclareerd in geërfde klassen en interfaces.
// het verkrijgen van alle klassen, interfaces en enums die expliciet zijn gedeclareerd in ConcreteClass
Class >[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//print [class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass,
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum,
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));
Krijg Verklarende Klasse
getDeclaringClass()
-methode retourneert het Class-object dat de klasse vertegenwoordigt waarin het is gedeclareerd.
Class > innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//print com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());
Ophalen van Pakketnaam
getPackage()
-methode retourneert het pakket voor deze klasse. De classloader van deze klasse wordt gebruikt om het pakket te vinden. We kunnen de getName()
-methode van Package oproepen om de naam van het pakket te krijgen.
//prints "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());
Class Modifiers Ophalen
getModifiers()
-methode retourneert de int-representatie van de class modifiers, we kunnen de java.lang.reflect.Modifier.toString()
-methode gebruiken om het in de stringindeling te krijgen zoals gebruikt in de broncode.
System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers()));
Type Parameters Ophalen
getTypeParameters()
retourneert de array van TypeVariable als er Type parameters zijn geassocieerd met de class. De typeparameters worden geretourneerd in dezelfde volgorde als gedeclareerd.
//Type parameters ophalen (generics)
TypeVariable >[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable > t : typeParameters)
System.out.print(t.getName()+",");
Geïmplementeerde Interfaces Ophalen
getGenericInterfaces()
-methode retourneert de array van interfaces geïmplementeerd door de class met generiek type-informatie. We kunnen ook getInterfaces()
gebruiken om de classrepresentatie van alle geïmplementeerde interfaces te krijgen.
Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));
Alle openbare methoden ophalen
getMethods()
methode retourneert de array van openbare methoden van de Klasse inclusief openbare methoden van zijn superklassen en superinterfaces.
Method[] publicMethods = Class.forName("com.journaldev.reflection.ConcreteClass").getMethods();
//prints openbare methoden van ConcreteClass, BaseClass, Object
System.out.println(Arrays.toString(publicMethods));
Alle openbare constructeurs ophalen
getConstructors()
methode retourneert de lijst van openbare constructeurs van de klasse referentie van het object.
//Alle openbare constructeurs ophalen
Constructor >[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints openbare constructeurs van ConcreteClass
System.out.println(Arrays.toString(publicConstructors));
Alle openbare velden ophalen
getFields()
methode retourneert de array van openbare velden van de klasse inclusief openbare velden van zijn superklassen en superinterfaces.
//Haal alle openbare velden op
Field[] publicFields = Class.forName("com.journaldev.reflection.ConcreteClass").getFields();
//Print openbare velden van ConcreteClass, zijn superklasse en superinterfaces
System.out.println(Arrays.toString(publicFields));
Haal alle annotaties op
getAnnotations()
methode retourneert alle annotaties voor het element, we kunnen het gebruiken met klassen, velden en methoden ook. Let op dat alleen annotaties beschikbaar met reflectie met behoudsbeleid van RUNTIME zijn, bekijk Java Annotaties Zelfstudie. We zullen hier later in meer detail naar kijken.
java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//Prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));
De Reflection API biedt verschillende methoden om klassenvelden te analyseren en hun waarden op runtime te wijzigen, in deze sectie zullen we enkele van de veelgebruikte reflectiefuncties voor methoden bekijken.
Krijg Openbare Veld
In de vorige sectie hebben we gezien hoe we de lijst met alle openbare velden van een klasse kunnen krijgen. De Reflection API biedt ook een methode om een specifiek openbaar veld van een klasse te krijgen via de getField()
methode. Deze methode zoekt naar het veld in de gespecificeerde klasseverwijzing en vervolgens in de superinterfaces en vervolgens in de superklassen.
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
De bovenstaande oproep zal het veld teruggeven van BaseInterface dat geïmplementeerd wordt door ConcreteClass. Als er geen veld wordt gevonden, wordt er een NoSuchFieldException gegenereerd.
Veld Declarerende Klasse
We kunnen getDeclaringClass()
van het veldobject gebruiken om de klasse te krijgen die het veld declareert.
try {
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
Class<?> fieldClass = field.getDeclaringClass();
System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
}
Krijg Veld Type
De getType()
-methode retourneert het Class-object voor het gedeclareerde veldtype. Als het veld een primitief type is, retourneert het het wrapperklasse-object.
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int
Waarde van Openbare Veld Ophalen/Instellen
We kunnen de waarde van een veld in een Object ophalen en instellen met behulp van reflectie.
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10
De get() methode retourneert een Object, dus als het veld een primitief type is, retourneert het de overeenkomstige Wrapper Klasse. Als het veld statisch is, kunnen we null doorgeven als Object in de get() methode. Er zijn verschillende set*() methoden om een Object aan het veld toe te wijzen of verschillende soorten primitieve types aan het veld toe te wijzen. We kunnen het type van het veld ophalen en vervolgens de juiste functie aanroepen om de veldwaarde correct in te stellen. Als het veld definitief is, gooien de set() methoden java.lang.IllegalAccessException.
Waarde van Privaat Veld Ophalen/Instellen
We weten dat private velden en methoden niet toegankelijk zijn buiten de klasse, maar met behulp van reflectie kunnen we de waarde van het private veld ophalen/instellen door de Java-toegangscontrole voor veldmodifiers uit te schakelen.
Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//toegangscontrole uitschakelen met onderstaande methodeaanroep
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"
Met reflectie kunnen we informatie krijgen over een methode en we kunnen deze ook aanroepen. In deze sectie zullen we verschillende manieren leren om een methode te verkrijgen, een methode aan te roepen en toegang te krijgen tot private methoden.
Krijg Openbare Methode
We kunnen getMethod() gebruiken om een openbare methode van een klasse te krijgen, we moeten de methode naam en parameter types van de methode doorgeven. Als de methode niet wordt gevonden in de klasse, zoekt de reflectie-API naar de methode in de superklasse. In het onderstaande voorbeeld krijg ik de put() methode van HashMap met behulp van reflectie. Het voorbeeld laat ook zien hoe de parameter types, methode modifiers en return type van een methode kunnen worden verkregen.
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//krijg method parameter types, print "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//krijg method return type, retourneert "class java.lang.Object", klasse referentie voor void
System.out.println(method.getReturnType());
//krijg method modifiers
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"
Oproepen van Openbare Methode
We kunnen de invoke() methode van het Method object gebruiken om een methode aan te roepen. In het onderstaande voorbeeld roep ik de put methode aan op een HashMap met behulp van reflectie.
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}
Als de methode statisch is, kunnen we NULL doorgeven als objectargument.
Het Aanroepen van Prive Methodes
We kunnen getDeclaredMethod() gebruiken om de private methode te verkrijgen en vervolgens de toegangscontrole uitschakelen om deze aan te roepen. Het onderstaande voorbeeld laat zien hoe we method3() van BaseClass kunnen aanroepen, die statisch is en geen parameters heeft.
// het aanroepen van een private methode
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"
De Reflection API biedt methoden om de constructors van een klasse te verkrijgen om te analyseren en we kunnen nieuwe instanties van de klasse maken door de constructor aan te roepen. We hebben al geleerd hoe we alle openbare constructors kunnen krijgen.
Krijg Openbare Constructor
We kunnen de methode getConstructor() gebruiken op de klasse representatie van het object om een specifieke openbare constructor te krijgen. Het onderstaande voorbeeld laat zien hoe de constructor van ConcreteClass hierboven gedefinieerd kan worden verkregen, en ook de constructor zonder argumenten van HashMap. Het laat ook zien hoe de array van parameter types voor de constructor kan worden verkregen.
Constructor > constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//constructor parameters ophalen
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
Constructor > hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
Instantieer Object met Constructor
We kunnen de methode newInstance() gebruiken op het constructor object om een nieuwe instantie van de klasse te maken. Omdat we reflectie gebruiken wanneer we de klasseninformatie niet hebben op compileertijd, kunnen we het toewijzen aan Object en vervolgens verder reflectie gebruiken om toegang te krijgen tot de velden en methoden ervan.
Constructor > constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//constructor parameters ophalen
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."
Constructor > hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap myMap = (HashMap) hashMapConstructor.newInstance(null);
Annotations werden geïntroduceerd in Java 1.5 om metadata-informatie van de klasse, methoden of velden te verschaffen, en worden nu veel gebruikt in frameworks zoals Spring en Hibernate. De Reflection API werd ook uitgebreid om ondersteuning te bieden voor het analyseren van de annotaties tijdens runtime. Met behulp van de Reflection API kunnen we annotaties analyseren waarvan het retentiebeleid Runtime is. Ik heb al een gedetailleerde tutorial geschreven over annotaties en hoe we de Reflection API kunnen gebruiken om annotaties te analyseren, dus ik zou je willen aanraden om de Java Annotations Tutorial te bekijken. Dat is alles voor de Java Reflection voorbeeldtutorial. Ik hoop dat je de tutorial leuk vond en het belang van de Java Reflection API begrepen hebt.
Source:
https://www.digitalocean.com/community/tutorials/java-reflection-example-tutorial