Accéder à une variable private dans la classe d’une API avec Reflection

Voleur accédant a une valeur privéeCertaines API, généralement dont les sources sont fermées ne permettent pas d’accéder à certaines valeurs d’un objet. En effet, dans la définition de leur classe, un champ a été mis à private et aucun getter n’a été mis à disposition. Ce problème m’est arrivé, alors que je développais un solution d’envoie de message par Bluetooth, un objet d’une API d’un développeur tierce contenait dans une variable privée contenant l’adresse MAC d’un utilisateur. Ne pouvant modifier le code source de mon collègue j’ai donc créé une classe respectant les propriété de cette classe tierce, mais permettant d’accéder en plus à ce champ privé.

Je vais ici vous décrire la solution que j’ai développé afin d’accéder à une variable private sans modifier le code source avec la méthode de la Reflection.

Considérons d’abord la classe PrivateDataClass contenant un champ privé dont on ne peut modifier le code source:

/**
 * Classe contenant des données privées auxquelles
 * nous aimerions avoir accés.
 */
public class PrivateDataClass {
	/**
	 * Nous n'avons pas accés à myPrivateInt.
	 * Mais nous aimerions!
	 */
	private int myPrivateInt = 77;
 
	/**
	 * Ceci est un exemple de méthode public
	 */
	public void myMethod() {
		System.out.println("This is a dummy method");
	}
}

Maintenant nous allons créer la classe héritant des propriétés de PrivateDataClass; PublicDataClass Nous implémenterons une méthode getMyPrivateInt() destinée à récupérer le champ myPrivateInt.

Schéma UML montrant PublicDataAccess permettant de récuperer une variable privée de PrivateDataAccess

Schéma UML montrant PublicDataAccess permettant de récuperer une variable privée de PrivateDataAccess

import java.lang.reflect.Field;
 
/**  Classe étandant une classe avec des champs privées. */
public class PublicDataClass extends PrivateDataClass {
 
	/** On met ici la classe parente dont on hérite les données. */
	private final static Class PARENT_CLASS = PrivateDataClass.class;
 
	/**
	 * Permet d'acceder au champs "myPrivateInt"
	 * de PrivateDataClass.
	 */
	public int getMyPrivateInt() {
		try {
			//1- on recupere la variable privée myPrivateInt de PrivateDataClass
			Field myPrivateField = PARENT_CLASS.getDeclaredField("myPrivateInt");
			//2- On la rend accessible
			myPrivateField.setAccessible(true);
			//3- On recupere l'objet Int
			Integer myObject = (Integer) myPrivateField.get(this);
			//4- L'autoboxing se chargera de le convertir en primitive int
			return myObject;
		} catch (Exception e) {
			e.printStackTrace();
		}
		//Valeur par défaut qui sera jamais atteinte
		return 0;
	}
}

Cette classe peut ensuite être utilisée à la place d’une PrivateDataClass, comme on peut le voir dans ce test de PublicDataClass.

/**
 * @author Eric VIALLE
 */
public class Main {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		PublicDataClass publicDataClass = new PublicDataClass();
 
		int value = publicDataClass.getMyPrivateInt();
		System.out.println("Private value: " + value);
 
		//Les autres méthodes de PrivateDataClass
		//sont toujours accessibles
		publicDataClass.myMethod();
 
		//PublicDataClass reste un PrivateDataClass
		//Si un objet de type PrivateDataClass est attendu,
		//On peut lui fournir PublicDataClass
		exemple(publicDataClass);
	}
 
	/**
	 * @param monObjet est un PrivateDataClass
	 */
	public static void exemple(PrivateDataClass monObjet) {
		System.out.println("PublicDataClass est " +
				"aussi un PrivateDataClass");
	}
}

Ce Main nous affichera:
Private value: 77
This is a dummy method
PublicDataClass est aussi un PrivateDataClass