mercredi 1 avril 2009

Utilisation des Generics en appli de gestion

Les generics permettent d'augmenter la productivité en factorisant le code commun à plusieurs cas d'utilisation.
Dans une application de gestion, beaucoup de taches sont redondantes, comme la lecture, l'ajout, la mise à jour et la suppression de données.
Ce petit guide expliquera brièvement comment factoriser ce type de code dans les différentes couches d'une application web de gestion.

Couche d'accès aux données

Cette classe permet d"effectuer des opérations de type CRUD (Create Read Update Delete) sur nimporte quelle entité gérée avec JPA.


public class CRUDService<T> {
@PersistenceContext
protected EntityManager em;

private Class<T> entityBeanType;

@SuppressWarnings("unchecked")
public CRUDService(Class clazz){
this.entityBeanType=clazz;
}

/**
* Récupération de tous les éléments à partir de first et jusqu'à count
*/
@SuppressWarnings("unchecked")
@Transactional
public List<T> findAll(int first, int count, String sort, boolean ascending){
Query query;
if (ascending)
query= em.createQuery("From " + entityBeanType.getName() + " order by " + sort + " asc");
else query = em.createQuery("From " + entityBeanType.getName() + " order by " + sort + " desc");
query.setFirstResult(first);
query.setMaxResults(count);
return query.getResultList();
}

/**
* Insertion d'un nouvel élément
* @param element
*/
@Transactional
public void add(T t){
em.persist(t);
}

/**
* Mise à jour d'un élément
* @param element
*/
@Transactional
public void update(T t){
em.merge(t);
}

/**
* Suppression d'un élément
* @param element
*/
@Transactional
public void delete(T t) {
em.remove(em.merge(t));
}


/**
* Nombre d'éléments
* @return
*/
@Transactional
public long getSize() {
Query query = em.createQuery("select count(e) From " + entityBeanType.getName() + " e");
return (Long)query.getSingleResult();
}
}


Ce service doit être étendu par un service spécifique pour pouvoir être injecté par un framework d'injection de dépendance comme Spring ou par introspection (voir plus bas).

@Service
public class PersonneService extends CRUDService<Personne>{
public CouleurService(){
super(Personne.class);
}
}

Ce service récupère donc toutes les méthodes de la classe CRUDService, adaptées à l'entité Personne. Il est donc capable de lister, ajouter, modifier et supprimer des objets de type Personne en base de données.
On peut ensuite injecter ce service dans une autre couche à l'aide de Spring :

@Resource PersonneService personneService;


Couche graphique
Il est possible de rendre la couche graphique générique si on utilise un framework full Java (comme Wicket, GWT...).
Dans notre exemple , on utilise une classe TableauCRUD<T> et une classe Formulaire<T> qui permettent de lister, de supprimer, d'ajouter et de modifier des données. Il suffit ensuite d'instancier ces classes avec le paramètre de son choix (par exemple la classe Personne) pour pouvoir l'utiliser.


new TableauCRUD<Personne>(Personne.class);


Libellés
Si des libellés sont utilisés dans les IHM, ils devront etre modifiables en fonction de l'entité utilisée (par exemple le titre de la page).On peut exporter ces libellés dans un fichier properties. par exemple on peut avoir la clé crud.tableau.titre.Personne= Administration des personnes


public String getTitle() {
return PropertiesLoader.getProperty("crud.tableau.titre."+entityBeanType.getSimpleName());
}


Introspection

Pour appeler une classe spécifique par son nom, par exemple pour instancier un service héritant de CRUDService (ou pour l'injecter avec Spring), on peut utiliser la réflexion :


Class.forName("monProjet.service"+entityBeanType.getSimpleName()+"Service").newInstance();



Attention cette technique nécessite de nommer les classes correctement (nomDeL'entité+Service) sans quoi elles ne seront pas retrouvées.On peut aussi injecter la référence d'un service Spring sans connaître le type paramétré, par exemple si on est dans la classe TableauCRUD<T>, à l'aide de la classe ApplicationContextHolder de Spring.


ApplicationContext context = ApplicationContextHolder.getContext();
CRUDService service = (CRUDService)context.getBean(entityBeanType.getSimpleName()+"Service");