Voorbeeldzelfstudie Java Reflection

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

  1. Reflectie in Java
  2. Java Reflectie voor Klassen
  3. Java-reflectie voor velden
  4. Java-reflectie voor methoden
  5. Java-reflectie voor constructors
  6. Java Reflectie voor Aantekeningen

  1. Reflectie in Java

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:

  1. JUnit – gebruikt reflectie om de @Test-aantekening te ontleden om de testmethoden te krijgen en deze vervolgens aan te roepen.
  2. Spring – afhankelijkheidsinjectie, lees meer op Spring Afhankelijkheidsinjectie
  3. Tomcat webcontainer om het verzoek naar het juiste module door te sturen door hun web.xml-bestanden en verzoek-URI te ontleden.
  4. Eclipse automatisch aanvullen van methodenamen
  5. Struts
  6. 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.
  1. Java Reflection voor Klassen

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));
  1. Java Reflectie voor Velden

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"
  1. Java Reflectie voor Methoden

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"
  1. Java Reflectie voor Constructeurs

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);
  1. Weerspiegeling voor Aantekeningen

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