mercredi 23 octobre 2013

Cours en ligne

Petite compilation de cours en ligne très didactiques (pour débutants) :




lundi 26 août 2013

Eclipse Kepler - Créer un projet web-maven avec WTP et M2E

Après quelques années de galère à essayer de faire cohabiter les plugins WTP, M2E et Maven for WTP, les utilisateurs d'Eclipse commençaient à s'impatienter (surtout en voyant d'autres IDE dans lesquels tout ça était plutôt naturel et simple). Mais cette fois on y est, enfin presque (ouf !)


Il aura fallu attendre Eclipse 4.3 ( "Eclipse Kepler" : http://www.eclipse.org/kepler/ ) pour que la distribution "Eclipse IDE for Java EE Developers" soit livrée avec les plugins Maven pré-installés.

Kepler (qui pèse un peu plus de 250 Mb) arrive avec :
  - Maven Integration for Eclipse ( "m2e" )   ver 1.4.0
  - Maven Integration for Eclipse WTP    ver 1.0.0
Ces même versions sont disponibles pour Eclipse Juno (mais à ajouter manuellement)

Voici donc comment créer un projet web avec Maven et WTP ...

1) Création du projet 


Il s'agit dans un premier temps de créer directement un projet Maven (et non WTP)
en utilisant l' archetype  "maven-archetype-webapp"

File  /  New (ou Alt-Shift-N)  /  Maven project

Sélection de l'archetype pour "web app" :



Après création, le nouveau projet apparait dans le workspace.
Le nom du projet est l' Artefact Id de Maven
Pour changer le nom il suffit de faire  " Refactor / Rename "

2) Structure du projet 


La structure du projet est maintenant presque prête.


Mais parfois il manque des répertoires standards de Maven qui n’ont pas été créés (ça semble se produire à partir du deuxième projet créé dans un même workspace).
Ce qui provoque des erreurs au niveau du build path :



Si c'est le cas, il suffit de les ajouter :
  - Dans « src/main » : new folder « java »
  - Dans « src » : new folder « test/java », et tant qu’on y est « test/resources »

3) Configuration du "pom.xml"


En ouvrant le fichier "pom.xml" on constate qu'il contient assez peu de chose.
Trop peu, puisqu'il n'y a même pas les dépendances aux libraires Servlet/JSP.

Il n'y a donc plus qu'à le compléter en fonction des besoins du projets

Exemples :

* Pour ajouter les JARs nécessaires aux Servlets 3.0 et au JSP 2.2 (Tomcat 7, Jetty 8, Java EE 6)
dans "dependencies" :

<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>3.0.1</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>javax.servlet.jsp-api</artifactId>
   <version>2.2.1</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>jstl</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
</dependency>

Pour forcer la version de Java  (ici Java 7)
dans "build/plugins"  :

<plugin>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.0</version>
   <configuration>
      <source>1.7</source>
      <target>1.7</target>
   </configuration>
</plugin>

* Pour configurer la production du « war »
dans  "build/plugins" :

<plugin>
   <artifactId>maven-war-plugin</artifactId>
   <version>2.3</version>
   <configuration>
      <failOnMissingWebXml>false</failOnMissingWebXml>
   </configuration>
</plugin>

Après avoir enregistrer les modifications du « pom.xml » ,
faire " Maven / Update project… "
pour répercuter la configuration du « pom.xml » sur le projet Eclipse

Le projet doit finalement avoir la structure suivante :
Et il ne doit plus y avoir aucune erreur nul part.

4) Tester le fonctionnement 


- Test avec une JSP :
Lancer l’exécution comme s’il s’agissait d’un projet web WTP classique :
Run as / Run on server 
On arrive sur la page « Hello world » produite par la JSP « index.jsp »,
si on modifie cette JSP il suffit de rafraîchir la page pour prendre en compte les modifications (comme dans tout projet WTP).

- Test avec une servlet :
Créer une nouvelle servlet dans  « src/main/java » 
(avec les servlet 3.0 il n’est pas nécessaire de déclarer cette nouvelle servlet dans le web .xml, une annotation suffira)

Conclusion


Avec une installation Eclipse Kepler de base il est maintenant assez simple de gérer des projets web.
Pour ceux qui veulent aller un peu plus loin il ne reste plus qu'à créer ses propres archetypes Maven...

Pour plus d'informations :



lundi 19 août 2013

Eclipse - Les principaux raccourcis

Avec la pratique chacun a ses raccourcis préférés, ils deviennent vite des réflexes conditionnés.
Même après des années d'utilisation on peut encore en découvrir de nouveaux. Voici donc mon "Best Of" des raccourcis Eclipse (évidemment incomplet), initialement destiné à mes étudiants mais pouvant intéresser  tous les développeurs Java utilisant Eclipse.
Cette liste ne demande qu'à être complétée, toutes les suggestions sont donc les bienvenues ...


Recherches (à partir de n'importe quelle vue) : 

 Ctrl - Shift - T    :  "Open Type"
Rechercher un "Type Java" (classe, interface, annotation, ... ) dans les sources (.java) et dans les .jar (.class)


 Ctrl - Shift - R    :  "Open Resource"
Rechercher un fichier dans le workspace (quel que soit le type du fichier)

Dans les deux cas différents patterns de recherche peuvent être utilisés :
Wilcards :
  *  n'importe quelle suite de caractères
  ?  n'importe quel caractère
  <  ou  "blanc" pour marquer la fin du pattern
Notation "camel case" :
Exemples :
  "TZ" : "TimeZone"
  "NuPoEx": java.lang.NullPointerException


Dans l'éditeur de code Java :

 Ctrl + S    :  "Save" 
Enregistre le fichier en cours d'édition

 Ctrl + ESPACE    :  "Content Assist" 
Propose une assistance à la saisie ("code completion")


 Ctrl + O    :  "Quick Outline" 
Affiche une fenêtre avec la liste des méthodes et des champs définis dans la classe en cours
Un deuxième "Ctrl+O" affiche toutes les méthodes et les champs (classe en cours + héritage )


 Ctrl + Shift + O   :  "Organize Imports"
Ajoute les imports nécessaires et réorganise la liste par ordre alphabétique

 Ctrl + Shift + F    :  "Format"
Formate le code (lignes de code sélectionnées ou toutes les lignes si aucune sélection)

 Ctrl + Shift + I    :  "Correct Indentation"
Corrige l'indentation du code pour les lignes sélectionnées.

 Alt + Shift + J    :  "Generate Comment"
Génère un bloc de commentaire de type JavaDoc pour la classe, la méthode ou le champ (en fonction de la position du curseur).

 Ctrl + Shift + /    :  "Add/Remove comment"
Ajoute ou supprime "//" au début de chacune des lignes sélectionnées

 Ctrl + T    :  "Quick Type Hierarchy" 
Affiche une fenêtre avec la hiérarchie de la classe en cours
Un deuxième "Ctrl+T" permet de changer le sens de la hiérarchie ( supertype / subtype )

 Ctrl + Alt + H    :  "Open Call Hierarchy" 
Affiche la vue "Call Hierarchy" pour la méthode sélectionnée


 Alt + Shift + B    :  "Show in Breadcrumb" 
Active le bandeau "breadcrum" dans les éditeurs de code Java
(pour désactiver le bandeau utiliser l'icône)

 Alt + Shift + O    :  "Mark Occurrences" 
Active le surlignage de toutes les occurrences identiques à celle qui est sélectionnée.
(raccourci de type "switch" : activation/désactivation)

 Ctrl + Shift + P    :  "Go to Matching Bracket"
Se place sur le { ou } correspondant à celui sur lequel se trouve le curseur 

 Shift + F2    :  "JavaDoc"
Affiche la JavaDoc pour le type sélectionné (ou la méthode sélectionnée) dans une vue de type navigateur.


Dans la vue "Package Explorer" :

 F4   :  Affiche la vue "Type Hierarchy" pour la classe sélectionnée (héritage)



Référence et déclaration Java :

Dans le code, sélectionner un type (classe, interface, ...), puis :

 F3   :  "Open Declaration"
Ouvre le fichier ".java" correspondant au type sélectionné

 Ctrl + G   pour rechercher la déclaration de ce type dans le workspace ( fichier ".java" dans les sources ou ".class" dans un .jar)
(ou click-droit + "Declarations") 

 Ctrl + Shift + G  pour rechercher les références à ce type dans le workspace
(ou click-droit + "References") 


Divers :

 Ctrl + M    :   pour passer la vue courante en taille maximale/normale (switch)

 Ctrl + F8    :   pour basculer vers une autre perspective

 Alt + Shift    :  bascule clavier français - clavier américain  (switch)
NB : Cette combinaison de touches est souvent utilisée par inadvertance ( notamment pour l'ajout de commentaires avec Alt+Shift+J ) 


Tous les raccourcis :

Enfin, pour connaitre tous les raccourcis disponibles, il suffit d'aller voir dans les préférences d'Eclipse :
Menu : "Window" - "Preferences",  Rubrique : "General" - "Keys" 

ou d'utiliser le raccourci   Ctrl + Shift + L 




mardi 9 avril 2013

Créer un launch Maven sous Eclipse avec le plugin m2e


Quand on utilise Maven en ligne de commande il est facile d’automatiser le lancement d’une tâche en créant un fichier de commande .sh ou .bat qui définira la configuration de la commande à lancer.
Exemple : mvn sonar:sonar sans les test, en mode debug , etc…

Mais dans Eclipse, qu’en est-il ?

De base le plugin « m2e » propose les lancements usuels de Maven ( build, clean, install, etc ) :


Ce qui permet de réaliser les opérations de base, mais elles se révèlent vite insuffisantes.

Puisqu’Eclipse utilise "Run as..." pour lancer Maven, il suffit de définir une nouvelle configuration pour pouvoir ensuite la lancer.

Remarque : "Run as..." est systématiquement disponible, pour tous les types de projets Eclipse, même pour un projet de type "General". Les configurations proposées varient en fonction du type de projet, mais on dispose toujours au moins de "Run Configurations…" qui donne accès aux différentes configurations définies dans le workspace.




1 - Définir une nouvelle configuration de lancement 


Il suffit donc de passer par "Run As / Run Configurations" pour obtenir la fenêtre suivante :



On y trouve toutes les configurations de lancement déjà définies, dont celles de Maven

Pour créer une configuration Maven :

  • Sélectionner "Maven Build"
  • Cliquer sur l’icône "New" pour créer une nouvelle configuration



Dans l’onglet "Main" indiquer

  • le nom de la configuration
  • le répertoire de base, utiliser une variable Eclipse plutôt qu’un chemin dans le filesystem ou dans le workspace,  ${project_loc} est la variable la mieux adaptée puisqu’elle contient le path absolu du projet sélectionné
  • le goal (ou les goals), par exemple "sonar:sonar", "clean install", etc…



Dans l’onglet "JRE" il est important de sélectionner le JRE qui va servir à l’exécution de Maven et notamment à compiler les sources (il faut donc référencer un JDK complet incluant tools.jar qui contient le compilateur Java )
Pour être cohérent, ce JRE doit être le même que celui du projet Java dans Eclipse



Enregistrer la configuration avec "Apply"

Pour lancer la configuration, utiliser "Run As – Run Configuration", sélectionner la configuration qui vient d’être créée, puis "Run".


2 - Enregistrer une configuration de lancement dans un fichier


Créer une configuration spécifique c’est très bien, mais si on pouvait la conserver dans un fichier, lui-même enregistré dans le projet concerné, ça permettrait de recopier ce fichier en cas de besoin, de le stocker avec les sources dans SVN ou GIT, etc…

Pour ce faire :
  • Ouvrir les configurations ( "Run As – Run Configuration" )
  • Sélectionner la configuration à enregistrer
  • Dans l’onglet "Commons" : 
    • remplacer "Save as – Local file" par "Save as – Shared file", 
    • utiliser "Browse" pour sélectionner le projet 
    • le nom du fichier sera le nom de la configuration avec l’extension ".launch"


Utiliser "Apply" pour enregistrer dans le fichier
Le fichier  "nom_de_configuration.launch"  apparaît dans le projet
Si on l’ouvre par un "double-click" on constate qu’il s’agit d’un simple fichier XML.

Pour lancer la configuration à partir du fichier, il suffit de faire un "click-droit" + "Run As" et Eclipse propose immédiatement la configuration sélectionnée dans le menu contextuel.
Elle peut donc maintenant être lancée directement par le fichier.

Si le fichier ne contient aucune information spécifiquement liée au projet (chemin absolu, JRE particulier, etc) il pourra être copié d’un projet à autre, et réutilisé à volonté.

---
Tutoriel rédigé grâce aux conseils avisés de Stéphane (http://techasite.blogspot.fr/)

mardi 5 mars 2013

GitHub avec Eclipse et "eGit" en 15 minutes

On ne présente plus GIT et GitHub. En y ajoutant Eclipse et le plugin "eGit" on disposera d'un environnement simple et efficace pour gérer des projets open source facilement partageables.

Rappel de l'architecture :



Pour la mise en place de l'ensemble, voici la marche à suivre...

1) Créer un "remote repository" sur GitHub


Première étape (si ce n'est déjà fait) se créer un compte sur GitHub :
un nom, une adresse mail, un mot de passe ... et hop c'est fait.

Une fois identifié, chaque utilisateur GitHub peut créer plusieurs "repositories".
Pour créer un repository : utiliser l'icône "Create a new repo" :


Il n'y a plus qu'à indiquer le nom du nouveau repository, sa description et à cliquer sur "Create repository" :

NB : Ne pas sélectionner "Initialize this repository with a README"
de façon à avoir un repository totalement vide, ce qui permet de suivre les étapes décrites ci-dessous.
( si le "repository" n'est pas vide, il faut passer par un import ).

Le "remote repository" est prêt à recevoir du contenu

2) Installer et configurer le plugin Eclipse pour GIT  ( "eGit" )


* Installation 
Comme pour tout plugin Eclipse, le plus simple est de passer par « Eclipse Marketplace »
( Menu : « Help » / « Eclipse Marketplace » ), de rechercher  « egit » et de l’installer :


* Configuration
Après installation du plugin, la configuration de GIT est accessible via "Window" / "Preferences" / "Team" / "GIT" 


C'est ici que l'on peut définir le répertoire qui contiendra les "repositories" locaux, ainsi que la configuration globale de GIT ( user, proxy http, etc )

NB : sous Windows l'emplacement des répertoires et des fichiers GIT est déterminé par la variable d'environnement "HOME".  Si cette variable n'est pas définie, ce sont les variables "HOMEDRIVE" + "HOMEPATH" qui sont utilisées.  Si "HOMEDRIVE" n'est pas définie, c'est "HOMESHARE" qui sera utilisée.



Configuration du répertoire des "local repositories"

Par défaut le répertoire des "local repositories" est "%HOME%/git".  Il peut être modifié avec "Browse"


Configuration utilisateur

Dans "Configuration" / "User settings" 
NB : la configuration globale de GIT pour l'utilisateur est stockée dans le fichier caché ".gitconfig" situé dans le répertoire défini par la variable d'environnement "HOME" ( ou HOMEDRIVE+HOMEPATH, ou HOMESHARE). L'emplacement de ce fichier ne peut pas être modifié par Eclipse.

C'est ici que sont définies (entre autres) les propriétés "user.name" et "user.email", définissant le nom d'utilisateur et son adresse email qui sont utilisés lors de chaque commit (ce qui permet d'avoir l'historique des commit).


La configuration de chaque repository ( "Repository Settings") sera accessible ultérieurement, quand les repositories auront été créés.


Eclipse est maintenant en capacité de gérer le contenu d'un projet avec un repository GIT

3) Associer un projet existant à un repository GIT


Si nécessaire créer un nouveau projet dans le workspace.

Comme pour Subversion, c’est tout le projet qui doit être associé à un gestionnaire de sources.
C’est donc au niveau du projet que va se faire l’association avec le repository.
Il suffit de faire un "click droit" sur le projet, puis "Team" / "Share Project"


Choisir "Git" + "Next"


A ce stade le "local repository" n'existe pas encore, il va donc falloir le créer :
  • cliquer sur "Create"
  • choisir le répertoire du "repository"
    l'emplacement proposé par défaut est celui qui a été défini dans la configuration
    il est possible de le changer avec "Browse" : pour cela, sélectionner le répertoire qui va contenir le "local repository" ( ex : D:\GIT-TESTS ) 
  • indiquer le nom du "repository"


Puis "Finish"


Cette fois le champ "Target Location" est renseigné et contient le chemin d'accès au "local repository"

Cliquer sur "Finish"

EGit va créer le "local repository" et déplacer le projet du workspace vers le repository.
( NB : le projet n'est plus physiquement dans le répertoire du workspace )

Des icônes spécifiques au gestionnaire de source apparaissent pour tous les fichiers du projet
Le point d'interrogation signifie que l'on n'a pas encore indiqué si le fichier devait être géré par GIT.

Nous pouvons maintenant gérer les différentes versions des fichiers sources dans le "local repository".

Les commandes GIT ( commit, etc ) sont accessibles par un click-droit + "Team"

Certains fichiers propres à chaque développeur n'ont rien à faire dans le repository des sources.
Pour les exclure il suffit d'afficher la vue "Navigator" ( "Window" - "Show View" - "Navigator" ) qui permet de voir tous les fichiers du projet et pour chaque fichier à exclure "Team" - "Ignore"  ( le point d'interrogation disparaît, GIT sait qu'il doit les ignorer).

Il ne reste plus qu'à faire un premier "commit" pour tous les fichiers du projet...

"Commit" sur le "local repository"

A la racine du projet : "Click-droit" - "Team" - "Commit" :
  • Indiquer le message du commit 
  • Sélectionner les fichiers concernés ( une sélection globale est possible avec "Select all" )


Et choisir "Commit" c'est à dire un commit sur le "local repository".

Cette opération peut être répétée aussi souvent que nécessaire, les différentes versions des fichiers du projet sont conservées par le "local repositoty" sans aucun impact sur le "remote repository".

Les fichiers modifiés depuis le dernier commit sont marqués avec ">"


"Push" sur le "remote repository"

Une fois que le projet a atteint une certaine maturité, il est nécessaire de faire un "Push" sur le "remote repository".

Pour ce faire :  "Click-droit" - "Team" - "Remote" - "Push" :


( autre possibilité : passer par "Team" - "Commit" et utiliser le bouton "Commit and Push"  )

A ce stade GIT ne connait pas le "remote repository" (celui de GitHub), il va donc falloir lui indiquer :

  • Sur GitHub, aller dans la liste des repositories, sélectionner le repository voulu et copier son URL ( le bouton "Copy to clipboard" est fait pour ça )
  • Copier cette URL dans la fenêtre d'Eclipse

  • pour le premier "Push" il faut indiquer le "User" et le "Password" de GitHub
  • bouton "Next >"
  • Sélectionner la branche ( "Source ref" et "Destination ref" )  puis  "Add Spec"
  • La "spécification" est ajoutée à la liste des "Specifications for push"


  • bouton "Next >"
  • la fenêtre de confirmation du "push" permet de vérifier les opérations qui seront réalisées
  • bouton "Finish >"
  • le résultat du "push" est affiché 
  • bouton "OK"
Et voila,  les fichiers sont maintenant à jour dans le "remote repository" sur GitHub.





4) Dissocier un projet existant de son repository GIT



Sur le projet : "click-droit" - "Team" - "Disconnect"
Le repository GIT existe toujours mais il n'est plus attaché au projet Eclipse



Quelques liens sur "GIT" et "eGit" 







mercredi 6 février 2013

Configuration de Subversion pour Eclipse avec Subclipse

"Subclipse" ( le plugin Eclipse pour Subversion ) est un classique.
Cependant, son utilisation avec un proxy peut poser quelques problèmes.
Il n'est pas rare d'être confronté à l'erreur suivante :
RA layer request failed svn: OPTIONS of ... 
quand la connexion change (avec proxy, sans proxy, changement de proxy, etc... )

Le premier réflexe est d'aller voir la configuration du proxy dans Eclipse, mais rien n'y fait.
L'explication est que si on utilise l'interface JavaHL JNI (cf "Window/Preferences/Team/SVN - SVN interface" ) c'est la configuration native de Subversion (au niveau du système d'exploitation) qui est utilisée. 

Il faut donc descendre d'un niveau et aller vérifier la configuration de la couche cliente de Subversion dans les  fichiers du système.

Sous Windows, à l'emplacement de la configuration de l'utilisateur se trouve un répertoire "Subversion"
( quelque chose du genre "C:\Documents and Settings\(USERNAME)\Application Data\Subversion"
ou "(USERS)\(USERNAME)\AppData\Roaming\Subversion" sous Windows 7 )

Dans ce répertoire, on trouve les fichiers "config" et "servers" :
. "config" sert à configurer le comportement "côté-client"
. "servers" sert à configurer les paramètres de connexion au serveur et notamment le proxy

Il suffit donc d'éditer le fichier "server" avec un éditeur de texte et de vérifier la configuration du proxy :
  • http-proxy-host = le.serveur.proxy
  • http-proxy-port = le.port.du.serveur.proxy
  • (et les autres paramètres du proxy si nécessaire )

Quand cette configuration est correcte, l'utilisation de Subclipse ne pose plus de problème, même en supprimant la configuration du proxy dans Eclipse !

Une autre méthode consiste à utiliser TortoiseSVN ( http://tortoisesvn.net/ ) pour gérer la configuration native de Subversion ( "Settings/Network" ) ce qui permet également de tester la connexion indépendamment d'Eclipse.

Voir aussi  http://java-espresso.blogspot.fr/2011/09/orgtigrissubversionjavahlclientexceptio.html

lundi 29 octobre 2012

L’encapsulation ne résiste pas à l’introspection


L’introspection Java, basée sur la « Java Reflexion API »,  permet de découvrir la structure de n’importe quelle classe. Ce principe permet donc de connaitre tous les éléments constitutifs d’une classe (champs, constructeurs, méthodes, ...) et d'agir sur ces éléments. Tous développeurs Java connaissent les usages classiques de cette API, mais si vous lisez cet article jusqu'à la fin vous verrez qu'une utilisation poussée de l'introspection permet d'aller extrêmement loin, par exemple jusqu'à modifier la valeur de la constante "TRUE" de la classe "Boolean" !   Etonnant, non ?

Utilisation basique de l'introspection 

Un des principaux intérêts de la réflexion est de pouvoir récupérer dynamiquement les valeurs des champs et d’invoquer les méthodes d’un objet dont on ne sait rien et sans avoir besoin du code source. Ce qui permet aux frameworks et autres bibliothèques Java en tous genres de récupérer des informations à partir, par exemple, d’un nom symbolique.

Ainsi,  avec l’ Expression Language dans les JSP l’expression « ${employe.nom} » sera analysée, puis la méthode « getNom() » de l’objet nommé « employe » sera invoquée et le résultat inséré à la place de l’expression.

Donc l’introspection c’est pratique, c’est même indispensable et jusque là tout va bien…
Mais l’introspection ne se limite pas à la consultation des objets, il est également possible de changer la valeur des champs et d’invoquer des méthodes qui modifient les données d’un objet (typiquement en invoquant des « setters »).

Alors qu’en est-il  de la sécurité ? 

Par défaut, les principes de sécurité qui sont appliqués à l’introspection sont les mêmes que ceux qui régissent les droits d’accès aux champs et aux méthodes d’un objet quand on utilise une classe lors du développement.

Donc  les méthodes et les champs privés d’une classe ne sont pas accessibles.

Exemple 1 – Tentative de modification d'un champ « private »  :
Field field = null ;
try {
   field = Employee.class.getDeclaredField("salary"); // Champ "private"
} catch (SecurityException e) {
   e.printStackTrace();
} catch (NoSuchFieldException e) {
   e.printStackTrace();
}

try {
   field.setDouble(employee, 1234.56); // Tentative de modification
} catch (IllegalArgumentException e) {
   e.printStackTrace();
} catch (IllegalAccessException e) {
   e.printStackTrace();
}


L'exécution de ce code va provoquer une Exception :
IllegalAccessException : Class .... can not access a member of class .... with modifiers "private"
puisque le champ "salary" est "private"


Comment les frameworks font-ils pour valoriser des champs privés ?

Les ORM (JPA, Hibernate, etc.. )  réussissent pourtant à valoriser des champs privés après un accès à la base de données
public class Employee {
   @Id
   @Column(name="EMP_ID")
   private int id; // Champ valorisé par JPA
   @Column(name="NAME")
   private String name; // Champ valorisé par JPA
}

Et c’est la même chose pour l’injection de dépendance…
public MyClass {
   @Inject
   private MyResource resource; // Injection (affectation d'une instance)
}

Il y a évidemment une solution :  il suffit d’appeler la méthode
« setAccessible(true) » sur le champ concerné pour le déverrouiller et le rendre immédiatement accessible, c'est-à-dire modifiable.


Exemple 2 - utilisation de setAccessible :
field.setAccessible(true);
field.setDouble(employee, 1234.56); // Et la tout se passe bien

L’appel  à  « setAccessible() » supprime les contrôles d’accès aux champs, méthodes et constructeurs (méthode héritée de la classe AccessibleObject)



L’utilisation de « setAccessible() »  est soumise à l’autorisation du « SecurityManager »
(cf java.lang.SecurityManager), un objet qui définit la politique de sécurité d’une application.

Pour obtenir le SecurityManager s’il existe :
SecurityManager secManager = System.getSecurityManager() ;
Dans certains environnements particuliers un SecurityManager peut être présent, notamment pour tout ce qui concerne le code téléchargé (Applets, Java Web Start, etc… ).  Mais par défaut la JVM n’a pas de SecurityManager, ce qui permet notamment d’utiliser JPA et l’injection de dépendance sans aucun problème dans pratiquement tous les cas.


Et si le champ est « final » ?

En principe un champ « final » n’est pas modifiable à postériori… Mais avec la « reflexion API » c’est possible, modifier un champ « private final » se fait exactement comme pour un champ « private »
non « final »

Et si le champ est « final static » ?

Alors là ça change tout !
Il s’agit d’un champ porté par la classe et qui sert de constante, donc  la « reflexion API » a quelques scrupules à nous laisser le modifier à tout moment. Et donc, même avec un appel préalable à  « setAccessible(true) » une exception sera levée :
IllegalAccessException: Can not set static final …. field …. to … 
Et on se dit que c’est tant mieux, car ce serait un peut cavalier de modifier des constantes…

Hummm….  Pourtant en poussant la réflexion (c’est le cas de le dire) un peu plus loin on arrive à la conclusion suivante : c’est le « final » qui pose problème quand il s’applique à un champ « static ».
Et qu’est-ce qui matérialise cette caractéristique « final » ? C’est un des « modifiers » récupérable avec « getModifiers() ».

Exemple 3 - utilisation des "modifiers" :
int fieldModifiers = field.getModifiers(); 
if ( Modifier.isStatic(fieldModifiers) ) {
   if ( Modifier.isFinal(fieldModifiers) ) {
      // static et final
   }
}

Donc, si on supprime le « modifier »  « final » ça va passer ?   He bien  oui !

Ce qui veut dire qu’un champ qui était « private » peut devenir « public » ou perdre sa
caractéristique  « final » ?
Et oui, en effet…   Il suffit de modifier les « modifiers » du champ.

Comment modifier les « modifiers » d’un champ ?

Les « modifiers » sont stockés dans un champ « private » de type « int » de la classe « Field », il faut donc s’attaquer directement à cette classe ( Field ) pour rendre ce champ accessible et donc de  pouvoir ensuite le modifier.
On va donc introspecter et modifier les classes qui servent à l'introspection (oui, on pousse le bouchon un peu loin, mais on s'arrêtera là,  promis...).

Le principe est le suivant :
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true); // "modifiers" est maintenant modifiable
Une fois accessible, il suffit d’affecter la nouvelle combinaison de « modifiers » à ce champ
modifiersField.setInt(targetField, newModifiers ) ;
( des exemples complets sont fournis un peu plus loin )

Comment l’introspection remet en cause l’immuabilité des objets

En Java un attribut est immuable s’il est « private » et que la classe ne fournit aucune méthode permettant de le modifier.
Ainsi, depuis les premiers cours d’initiation à Java on nous a expliqué que les instances de « Boolean » et « String » par exemple étaient immuables. Et pourtant, en poussant l’introspection dans ses derniers retranchements il est tout à fait possible de modifier de type d’objets (voir les exemples ci-après)

Voici quelques exemples d’utilisation un peu « border line » de l’introspection qui démontrent qu'on peut aller très loin (trop loin ?)...

NB : Ces exemples sont potentiellement dangereux et n'ont pas vocation à être utilisés dans une application réelle.


Exemple A  – Modifier le contenu d’une instance de la classe "Boolean"

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Modification d'une instance de Boolean avec la Reflexion API
 * Copyright (c) 2012 Laurent Guerin - Licence LGPL v3
 */
public class ChangeBoolean {

 public static void main(String[] args) {
  
  Boolean b1 = new Boolean(true) ;
  
  System.out.println(" b1  : " + b1 + ", b1.booleanValue() : " + b1.booleanValue() );
  
  try {
   changeValue( b1, false ) ;
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  System.out.println(" b1  : " + b1 + ", b1.booleanValue() : " + b1.booleanValue() );  
 }

 /**
  * Change la valeur d'un booleen
  * @param instance du booleen a modifier 
  * @param newValue valeur a affecter
  * @throws Exception
  */
 public static void changeValue(Boolean instance, boolean newValue) throws Exception {

  // La valeur est portee par le champ "value" de la classe "Boolean"
  Field field = Boolean.class.getDeclaredField("value") ;
   
  int targetFieldModifiers = field.getModifiers();
  if ( Modifier.isFinal(targetFieldModifiers) ) System.out.println(" field is 'final' ");
  if ( Modifier.isPrivate(targetFieldModifiers) ) System.out.println(" field is 'private' ");
  // Le champ "value" est "final" et "private"
  
  field.setAccessible(true) ;

  System.out.println(" original value : " + field.get(instance) );
  System.out.println(" change value to : " + newValue);
  field.set(instance, newValue) ;
 }
}

Exemple B  – Modifier le contenu d’une instance de la classe "String"

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Modification d'une instance de String avec la Reflexion API
 * Copyright (c) 2012 Laurent Guerin - Licence LGPL v3
 */
public class ChangeString {

 public static void main(String[] args) {
  
  String s = "Hello world !" ;
  String s2 = new String(s) ;
  
  System.out.println("s = " + s  + " s.hashCode = " + s.hashCode());
  System.out.println("s2 = " + s  + " s2.hashCode = " + s2.hashCode());
  System.out.println("s == s2 ?  " + ( s == s2 ) );
  System.out.println("s.equals(s2) ?  " + ( s.equals(s2) ) );
  
  try {
   System.out.println("Convert to uppercase...");
   toUpperCase( s ) ;
   System.out.println("s  = " + s  + " hashCode = " + s.hashCode());
   
   System.out.println("Change value ...");
   populate(s, "ABCDEFGHIJ") ;
   System.out.println("s = " + s  );
   
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  System.out.println("s  = " + s  + " s.hashCode = " + s.hashCode());
  System.out.println("s2 = " + s2  + " s2.hashCode = " + s2.hashCode());
  System.out.println("s.equals(s2) ?  " + ( s.equals(s2) ) );
  System.out.println("s.equals('ABCDEFGHIJ') ?  " + ( s.equals("ABCDEFGHIJ") ) );
 }

 /**
  * Converti la chaine en majuscules
  * @param s 
  * @throws Exception
  */
 public static void toUpperCase ( String s ) throws Exception {

  // Les caracteres qui composent une String sont dans le champ "value" (private)  
  // qui pointe sur un tableaux de "char" 
  Field valueField = String.class.getDeclaredField("value");
  // Hash code de la chaine 
  Field hashField   = String.class.getDeclaredField("hash"); 
   
  int targetFieldModifiers = valueField.getModifiers();
  if ( Modifier.isFinal(targetFieldModifiers) ) System.out.println(" field is 'final' ");
  if ( Modifier.isPrivate(targetFieldModifiers) ) System.out.println(" field is 'private' ");
  
  valueField.setAccessible(true) ; 
  hashField.setAccessible(true) ;

  String sUpperCase = s.toUpperCase() ;
  char[] newValue = sUpperCase.toCharArray(); // Same array size => no pb

  valueField.set(s, newValue ); 
  hashField.setInt(s, newValue.hashCode()); // Hash code
  // La conversion en majuscules conserve le meme nombre de caracteres 
  // il n'y a donc rien d'autre a changer
 }
 
 /**
  * Change la valeur d'une chaine
  * @param s chaine a modifier
  * @param newString chaine a affecter
  * @throws Exception
  */
 public static void populate ( String s, String newString ) throws Exception {
  // Caracteres de la chaine
  Field valueField  = String.class.getDeclaredField("value");
  // Offset sur le tableau de char
  Field offsetField = String.class.getDeclaredField("offset");
  // Nombre de caracteres dans le tableau
  Field countField  = String.class.getDeclaredField("count");
  // Hash code de la chaine 
  Field hashField   = String.class.getDeclaredField("hash"); 
  
  valueField.setAccessible(true) ;
  offsetField.setAccessible(true) ;
  countField.setAccessible(true) ;
  hashField.setAccessible(true) ;

  // Affichage des valeurs d'origine 
  char[] value = (char[]) valueField.get(s);
  String sValue = new String(value);
  System.out.println("value  = '" + sValue + "' ( length = " + value.length + ", hashCode = " + s.hashCode() + " )" );
  System.out.println("offset = " + offsetField.getInt(s) );
  System.out.println("count  = " + countField.getInt(s) );
  System.out.println("hash   = " + hashField.getInt(s) );

  // Nouvelle chaine a affecter 
  String newValue = newString ;
  valueField.set(s, newValue.toCharArray() );
  // Le nombre de caracteres change => mettre a jour les autres informations 
  offsetField.setInt(s, 0); // Offset : on demarre a 0
  countField.setInt(s, newValue.length()); // Nombre de caracteres
  hashField.setInt(s, newValue.hashCode()); // Hash code  
 }
}


Exemple C  – Modifier la valeur de la constante "TRUE" de la classe "Boolean"
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Modification d'une constante de la classe Boolean avec la Reflexion API
 * Copyright (c) 2012 Laurent Guerin - Licence LGPL v3
 */
public class ChangeBooleanTRUE {

 public static void main(String[] args) {
  System.out.println("=== AVANT : ");
  System.out.println(" Boolean.TRUE  : " + Boolean.TRUE  );
  System.out.println(" Boolean.FALSE : " + Boolean.FALSE );
  System.out.println(" Boolean.valueOf(true)  : " + Boolean.valueOf(true)  );
  System.out.println(" Boolean.valueOf(false) : " + Boolean.valueOf(false) );
  
  try {
   changeTRUEConstant( false ) ;
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  System.out.println("=== APRES : ");
  System.out.println(" Boolean.TRUE  : " + Boolean.TRUE  ); // false (!)
  System.out.println(" Boolean.FALSE : " + Boolean.FALSE );
  System.out.println(" Boolean.valueOf(true)  : " + Boolean.valueOf(true)  ); // false (!)
  System.out.println(" Boolean.valueOf(false) : " + Boolean.valueOf(false) );
 }

 
 /**
  * Change la valeur de la constante TRUE (!) 
  * @param newValue
  * @throws Exception
  */
 public static void changeTRUEConstant( boolean newValue ) throws Exception {

  // La constante TRUE ( final static ) 
  Field field = Boolean.class.getDeclaredField("TRUE") ;
  
  field.setAccessible(true) ;
  
  int fieldModifiers = field.getModifiers();
  
  if ( Modifier.isStatic(fieldModifiers) ) {
   if ( Modifier.isFinal(fieldModifiers) ) {
    // Ce champ est "final static" : il faut supprimer "final"
    removeFinalModifier(field);
   }
  }
  // Affectation de la nouvelle valeur 
  field.set(null, newValue) ;
 }
 
 /**
  * Supprime le modifier "final" du champ 
  * @param targetField
  * @throws Exception
  */
 public static void removeFinalModifier(Field targetField) throws Exception {

  Field modifiersField = Field.class.getDeclaredField("modifiers");
  
  modifiersField.setAccessible(true); // Les "modifiers" sont maintenant accessibles

  int targetFieldModifiers = targetField.getModifiers();
  
  modifiersField.setInt(targetField, targetFieldModifiers & ( ~Modifier.FINAL ) ) ; // Le champ n'est plus "final"
  
 }
}

Conclusion :  introspection = no limit !

En  conclusion, avec la « Java Reflexion API »  on peut pratiquement tout modifier dynamiquement, y compris des données réputées immuables. Ce qui ouvre des perspectives intéressantes mais potentiellement dangereuses. L’introspection est donc un outil puissant, mais à manipuler avec précaution.