Bla bla blog

Aller au contenu | Aller au menu | Aller à la recherche

jeudi 19 février 2009

Créer ses annotations

Spécifiées dans la JSR175 et apparues avec Java 5.0, les annotations sont désormais omniprésentes dans la plate-forme JavaEE et ses nombreux frameworks associés. L'occasion de revenir sur les techniques permettant de créer ses propres annotations.

Introduction

Les annotations que nous introduisons dans notre code sont à destination d'un "environnement d'exécution" qui va utiliser ces annotations pour effectuer des traitements supplémentaires ou configurer les traitements qu'il a l'habitude de faire. Ainsi si on prend le compilateur java comme "environnement d'exécution", l'annotation @Override lui indiquera que la méthode annotée a été redéfinie, le compilateur se chargera de vérifier qu'il s'agit bien d'une redéfinition. Notons que cette information ne sera nécessaire qu'en phase de compilation. Pour l'annotation @Deprecated qui indique si une méthode ou une classe est dépréciée, ainsi placée avant une méthode ou avant un classe, cette annotation n'aura pas le même sens.

Définition d'une annotation

Nous venons de le voir une annotation peut avoir une certaine portée, l'annotation @Deprecated n'était nécessaire que pour la phase de compilation. Par ailleurs certaines annotations s'appliquent à des méthodes, d'autres à des classes, des classes d'un certains type, etc... voilà les deux aspects que nous aurons à configurer sur nos annotations, et cette configuration se fera via... des annotations (ou des meta-annotations si vous préférez)

Définition de la portée ou "Rétention"

Vos classes Java, dans lesquelles vous placez vos annotations, passent par 3 états : code source, bytecode et bytecode interprété et chargé par le classloader de votre JVM. La méta-annotation @Retention vous permet d'indiquer dans quel états vous souhaitez utiliser votre annotation. Pour vous faciliter la tâches ces 3 valeurs sont stockées dans l'enum RetentionPolicy (SOURCE, CLASS, RUNTIME). Si on prend l'exemple de l'annotation @override, elle aura comme sera définie comme suit :

@Retention(RetentionPolicy.SOURCE)

A contrario des spécifications 3.0 des EJB ou des Servlets, les nombreuses annotations utilisées, le seront en phase d'exécution car le serveur d'application dans lequel vous déploierez vos composants aura besoin de découvrir les configuration associée à chacun de ces composants (url-pattern, name, init-param, session, stateless, stataful, entity, etc...).

Prenons l'exemple d'une annotation @Auteur qui nous permettrez d'indiquer dans le code quel est l'auteur de ce dernier, voici comment nous définirions une telle annotation. Dans le fichier Auteur.java :

package com.jcc.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {

}

Notez que l'annotation est placée dans un package comme n'importe quel élément (classe, enum, interface) Java et que le mot clé @interface permet d'indiquer qu'il s'agit de la définition d'une annotation

Définition de la cible ou "Target"

La meta-annotation @Target permet de définir une contrainte sur le contexte d'utilisation de notre annotation : classe, méthode, constructeur, attribut, etc. Là encore toutes les valeurs possibles sont stockées dans l'enum ElementType :

  • TYPE – s'applique uniquement au type java spécifié (classe, interface, enum, annotation)
  • FIELD – pour les attributs static ou d'instance d'une classe
  • METHOD – s'applique aux méthodes
  • PARAMETER – pour les paramètres d'une méthode
  • CONSTRUCTOR – destiné aux constructeurs
  • LOCAL_VARIABLE – pour les variables locales d'une méthode
  • ANNOTATION_TYPE – pour les annotations dont le type est à précises
  • PACKAGE – s'applique à l'intégralité du package indiqué

Imaginons que nous souhaitions appliquer notre @Auteur aux méthodes et aux constructeurs, voici comment nous devrions définir notre annotation :

package com.jcc.annotations;

import java.lang.annotation.*;

@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {

}

passage de propriétés

Dernier point, il est généralement nécessaire de passer des paramètres à nos annotations. Dans notre cas, il s'agirait de passer le nom de l'auteur qui souhaite signer la méthode ou le constructeur qu'il vient d'écrire. Pour pouvoir passer de tels paramètres il faut les déclarer lors de la définition de l'annotation comme suit :

package com.jcc.annotations;

import java.lang.annotation.*;

@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {

        String nom();
        
}

Si vous souhaiter rendre ce paramètre optionnel, donnez lui une valeur par défaut comme indiqué dans l'exemple ci-dessous :

package com.jcc.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auteur {

        String nom() default "Auteur par défaut";
        
}

Utilisation de l'annotation

Une fois cette annotation compilée vous pourrez l'utilisez directement avant vos déclaration de méthode ou de constructeur comme dans l'exemple :

public class ClasseDeTest {

        @Auteur
        public ClasseDeTest() {
        }
        
        @Auteur(nom="John Smith")
        public String toString() {
                return super.toString();
        }
}

Les informations sur l'auteur de cette méthode et de ce constructeur seront disponibles pour le compilateur, le classloader et la JVM, ainsi à l'exécution et via l'api d'introspection de Java il sera possible de retrouver l'auteur de cette méthode ou de ce constructeur.

Nouveautés servlet 3.0 (JSR 315) - Les annotations

Introduction

Cet article met l'accent sur la nouvelle série d'annotations introduites qui peuvent être utilisés par les développeurs plutôt que de mettre les données dans le fichier de configuration

Facilité de développement par Annotations

Les informations sur les composants Web tels que les Servlets, Servlet filtres sont mentionnées dans le descripteur de déploiement, qui est web.xml. A partir de Servlet 3.0, il est également possible de spécifier des méta-informations sur un élément (classe, interface), cette opération se fera via les annotations. Le fichier web.xml ne disparait pas pour autant. D'ailleurs les informations qu'il contient sont toujours prioritaires sur celles des annotations. Il est également possible d'activer ou pas la prise en charge des annotations par le conteneur via la balise <metadata-complete> directement placée sous la balise <web-app> du fichier web.xml. Cette balise attend les valeur "true" (le web.xml est complet, il se suffit à lui même) ou false (dans ce cas les annotations devraient être prises en compte)

Les annotations suivantes sont applicables à partir de spécifications Servlet 3.0,

    * @WebServlet 
    * @WebServletContextListener 
    * @ServletFilter 
    * @InitParam

Annotations WebServlet et InitParam

Dans cette section, nous allons voir l'utilisation de @WebServlet et @InitParam par un exemple.

package net.javabeat.servlet30.newfeatures;

import javax.servlet.annotation.InitParam;
import javax.servlet.annotation.WebServlet;

@WebServlet(
    name = "SimpleServlet", 
    urlPatterns = {"/simple"}, 
    initParams = {
        @InitParam(name = "param1", value = "value1"),
        @InitParam(name = "param2", value = "value2")}
)
public class SimpleServlet {

}


Dans cet exemple, nous avons créé une classe SimpleServlet qui n'hérite pas de HttpServlet, contrairement à ce que prévoit la spécification originale. En revanche, pour bénéficier de cette classe comme une classe Servlet, nous avons annoté en utilisant l'annotation @ WebServlet. Notez que le nom de la servlet est SimpleServlet comme spécifié par l'attribut name. L'attribut urlPatterns définit un ensemble de modèles url-qui peut être utilisé pour invoquer la Servlet. Voici le descripteur de déploiement équivalant :

<servlet>
    <servlet-name>SimpleServlet</servlet-name>
    <servlet-class>net.javabeat.servlet30.newfeatures.SimpleServlet</servlet-class>

        <init-param>
            <param-name>param1</param-name>
            <param-value>value1</param-value>
        </init-param>

        <init-param>
            <param-name>param2</param-name>
            <param-value>value2</param-value>
    </init-param>

</servlet>

<servlet-mapping>
    <url-pattern>/simple</url-pattern>
    <servlet-name>SimpleServlet</servlet-name>
</servlet-mapping>

Nouveautés servlet 3.0 (JSR 315) - Introduction

Après la révolution des EJB 3.0, les servlets leur emboitent le pas et passent à leur tour en version 3.0. Comme pour les EJB, la principale nouveauté correspond à une simplification de la spec avec des annotations à la place des fichiers descripteur de déploiement, mais ce n'est pas tout... Notez que cette spec n'est pas encore finalisée, certaines parties risquent de bouger encore un peu. Je me lance donc dans une suite d'articles consacrés à cette spécification et dans lesquels je compte successivement traiter :

  1. les annotations
  2. l'intégration des frameworks (struts, jsf, etc.)
  3. Les requêtes asynchrones

Notez que certains articles seront parfois inspirés d'autres post, voire de simple traduction, c'est pourquoi je tiens d'ores et déjà à indiquer les sources utilisées :

mercredi 11 février 2009

Mo-Du-La-Ri-Té, Modularité

Là où certains scandent le mot fraternité, d'autres plus proche du monde IT semblent adopter le mot modularité. Quand je pense que sur les bancs de l'école on nous disait alors que l'approche objet permettait la réutilisabilité, qu'on allait pouvoir travailler indépendamment sur des couches (modules) distinctes et réutilisables... En fait en prenant un peu de recule, en phase de développement effectivement nos packages nous permettent de correctement distinguer les couches d'accès au données, présentation, etc. En plus si on utilise des frameworks comme maven, la structure de notre projet fera clairement apparaître nos différents artefacts en vue du build. Mais force est de constater qu'une fois le build effectué, l'archive à déployer fera moins apparaître cette modularité. Prenons un EAR, certes il sera par exemple composé d'un war et d'un ou plusieurs JAR pour les couches de services et d'accès aux données, mais dans un tel découpage on retrouvera les classes métiers, les interfaces des façades, et toutes les classes communes aux différentes archives.

Java 7.0 et la modularité

Comme le fait remarquer Alex Moussine Pouchkine (Architecte Java chez Sun) : "le système de package de Java est obsolète et pose les mêmes problème que les DLL de Microsoft". La JSR 277 a donc vu le jour chez SUN pour remplacer le système d'archive actuel (jar, war et ear) et la première implémentation de cette JSR devrait voir le jour dans la prochaine version du JDK, soit Java 7.0. Issues de la spec, voici les réponses que devra apporter cette JSR 277 également appelée Java Module System (JMS rien à voir avec Java Messaging System) :

  1. Un nouveau format de distribution des classes java et ressources associées, ainsi qu'un nouveau format pour les métadonnées associées au module.
  2. Une spécification pour réaliser un référentiel de modules (exit les référentiels Maven ??)
  3. Les métadonnées indiqueront :
  • une description du contenu du module
  • les dépendances vis à vis d'autres modules en précisant les versions de ces derniers
  • l'ensemble des ressources (Java et autres) accessibles depuis d'autres modules

Je ne sais pas ce que vous en pensez, mais ça me rappelle étrangement la notion de bundle OSGi, comme les plugins Eclipse, dans lesquels on trouve exactement les mêmes notions. J'avoue partager le constat sur les limitations des archives d'un point de vue modularité, mais de là à définir un nouveau standard... serait-ce un choix politique plutôt que technique ? Pour aller plus loin, je vous conseille ce billet provenant de l'OSGi Alliance.

GlassFish et la modularité

Au delà de la prochaine version de Java, on parle également de la sortie prochaine de Java EE 6 et surtout du premier serveur d'application qui implémentera cette nouvelle mouture : GlassFish V3. Les atouts mis par Sun pour la nouvelle version de GlassFish sont : la modularité, l'extensibilité, la possibilité de déployer ou supprimer de nouveaux services "à chaud". Cette approche permettra de définir des profils d'utilisation de GlassFish et donc de supprimer certains services couteux (RMI, JMS, JNDI, EJB, WS, etc.), mais Sun nous indique qu'il sera possible de réinstaller ces services à tout moment et sans redémarrer le serveur d'application. Pour parvenir à ce résultat la plate-forme de GlassFish reposera sur... OSGi (?)

jeudi 29 janvier 2009

JAXP 1.3

Java 5 et les standards XML

Ayant eu l'occasion de me pencher sur les nouveautés de Java 5.0 et plus particulièrement sur le support des standards XML, il me semblait naturel de vous en faire profiter via ce billet.

Pour toutes les technos relatives à XML, les API du JDK sont regroupées en JAX (Java API for XML) et chaque JAX répond à une problématique technique:

  • JAXB pour le mapping Objet / XML
  • JAXRPC pour l'invocation de web service
  • JAXM pour l'envoi de message
  • JAXP pour la manipulation des documents XML, etc.

Les principales évolutions rencontrées concernent justement JAXP et en particulier la validation de documents XML et l'extraction de données via XPath.

Validation de documents XML

La validation de documents XML est une opération très pratique puisqu'elle permet de vérifier la structure du document XML mais également, pour certains types de schéma, le typage des données qu'il contient.

Cette opération était déjà possible dans les précédentes version du JDK, par contre

  • seul la syntaxe des DTD était supportée
  • la validation ne pouvait se faire qu'à la lecture du document XML
  • Le document XML devait être lier à son schéma

Le JDK 1.5 vient justement lever ces contraintes avec :

  • Le support d'autres syntaxes de schéma comme XML-Schema et Relax-NG qui entre autres valident le typage des données.
  • La validation peut se faire à n'importe quel moment, entre autre avant la génération d'un fichier XML pour s'assurer que son contenu est bien valide.
  • Il n'est plus nécessaire de lier les documents XML à leur schéma pour les valider, on pourra associer à une fabrique de parseurs un schéma, de telle sorte que tous les parseurs issus de cette fabrique valident les documents XML lors de leur lecture.
// Création de la fabrique de parseurs DOM 
DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();

// Création d'une fabrique de schémas supportant la //syntaxe XML-schema 
SchemaFactory schemaFactory =    SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 

// Création et compilation du schéma
Schema schema = schemaFactory.newSchema(new File(schemaFileName));

// Association de la fabrique de parseurs au schéma
factory.setSchema(schema);

DocumentBuilder parseur = factory.newDocumentBuilder()
parseur.parse(new File(xmlFileName));
  • On eut également créer un objet Validator capable de prendre n'importe quel document XML, n'importe quel schéma et de valider l'un par rapport à l'autre à tout moment (à la lecture du document XML ou après l'avoir modifié en mémoire)
// Créer un objet Schema 
SchemaFactory schemaFactory =SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 

Schema schema = schemaFactory.newSchema(new File(schemaFileName));

// Création de l'objet Validator, chargé de la validation quand il le souhaite…
Validator validator = currentSchema.newValidator();
          
//Déclenche la validation sur un arbre DOM
validator.validate(new StreamSource(XMLfileName));

Support natif de XPath

Le JDK 1.5 supporte désormais nativement XPath 1.0, c'est-à-dire qu'il vous est possible d'exécuter des requêtes XPath sur une représentation mémoire d'un document XML et donc d'en extraire plus facilement le contenu.

La représentation mémoire de votre document XML sera sous forme d'arbre DOM et se fera donc via l'analyseur du même nom : Il ne vous reste qu'à en extraire le contenu via un objet sur mesure fourni par JAXP : l'objet XPath.

XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();          
String nom = xPath.evaluate("/Personne/@nom",doc);
NodeList enfantsNodeList = (NodeList)xPath.evaluate("/Personne/Enfant", doc,XPathConstants.NODESET);

Sur cet exemple on constate qu'il suffit de créer un objet XPath et que ce dernier dispose d'une méthode evaluate() pour appliquer une requête à notre arbre DOM. Dans la spécification 1.0 de XPath, il existe quatre types : string, number, boolean et nodeset. Nous pourrons donc spécifier le type attendu en retour de la méthode evaluate() comme dans l'exemple ci-dessus où un esemble de nœuds est retourné.

Voilà pour ces quelques nouveautés de Java 5 au niveau de XML, Java 6 ne sera pas en reste en termes de nouveautés et plus particulièrement sur JAX-WS et la création des web services à la .Net (avec les annotations), mais aussi avec JAXB et un vrai mapping objet–XML, mais ces sujets seront l'occasion d'un nouveau billet.

IText pour la génération de pdf

iText (http://www.lowagie.com/iText/) est une librairie écrite en java et permettant de générer des documents aux formats PDF, RTF, HTML, etc. Cette librairie est gratuite et distribuée sous licence LGPL (GNU Lesser General Public License), vous avez donc accès au code source mais toute modification apportée sur ce dernier doit être mise à la disposition des autres utilisateurs.

L’API mise à notre disposition est très simple et permet de générer un document en quelques instructions. Voici un exemple permettant de générer un document au format A4, dans lequel on insère un simple paragraphe.

Document doc = new Document(PageSize.A4);
PdfWriter.getInstance(doc, new FileOutputStream("exemple1.pdf"));
doc.open();
doc.add(new Paragraph("Voici mon premier document"));
doc.close();

Si vous décidez de creuser un peu cette API, vous verrez qu’il existe un certain nombre de classes permettent d’insérer un tableau, une image, une entête, un pied de page et j’en passe…

Table table = new Table(3);             //Tableau à trois colonnes
table.addCell("Date");                        //Ajout de la première cellule
table.addCell("Durée");
table.addCell("Numéro appelé"); 
doc.add(table);                         //Ajout du tableau dans le doc

Voilà un rapide aperçu de cette librairie iText, notez qu’il existe d’autres librairies offrant les mêmes fonctionnalités, citons iTextSharp (http://itextsharp.sourceforge.net) pour la plate-forme .Net et FPDF (http://www.fpdf.org/) pour les inconditionnels de PHP.

lundi 20 octobre 2008

Comment effectuer un traitement au démarrage d'une application web ?

Mécanisme souvent méconnu, les listeners sont de simples classes Java qui seront automatiquement invoquées par le conteneur de JSP/Servlet lors du démarrage ou de l'arrêt de votre application. Comme les JSP ou les servlets, les listeners sont des composants, nous allons donc étudier leur contrat technique et leur mode de déploiement au travers d'un exemple simple : créer une connexion sur une base de données au démarrage de l'application et la fermer lors de l'arrêt de celle-ci

Contrat technique

Un listener est une simple classe Java qui implémente l'interface javax.servlet.ServletContextListener, et redéfinit donc les méthodes contextInitialized(ServletContextEvent) et contextDestroyed(ServletContextEvent) qui seront respectivement appelée lors du démarrage et de l'arrêt votre application. Notez que ces deux méthodes disposent d'un objet de type ServletContextEvent en paramètre. Cet objet est très précieux car il vous permet d'accéder au ServletContext (également appelé "scope" application) de votre application. Ainsi en stockant nos paramètres de connexion à la base de données dans le fichier web.xml, voilà comment nous pourrions créer notre connexion avant de la placer dans le scope application :

package com.juliencarette.listeners;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyListener implements ServletContextListener{

        private Connection cnx=null;
        
        @Override
        public void contextInitialized(ServletContextEvent evt) {
                try {
                        ServletContext servletContext = evt.getServletContext();
                        String driver = servletContext.getInitParameter("jdbc.driver");
                        String url = servletContext.getInitParameter("jdbc.driver");
                        String login = servletContext.getInitParameter("jdbc.username");
                        String pwd = servletContext.getInitParameter("jdbc.password");
                        
                        Class.forName(driver);
                        cnx = DriverManager.getConnection(url, login, pwd);
                        servletContext.setAttribute("myCnx", cnx);
                
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
        
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvt) {
                try {
                        if(cnx!=null){
                                cnx.close();
                        }
                } catch (SQLException e) {
                        e.printStackTrace();
                }
        }

}

Déploiement

Comme pour les servlets, les listeners sont des classes, les classes compilées de vos listeners devront être placées dans le répertoire /WEB-INF/classes de votre application. Par ailleurs il vous faut les renseigner de la manière suivante dans le fichier web.xml de votre application.

<listener>
      <listener-class>com.juliencarette.listeners.MyListener</listener-class>
</listener>

Histoire de tester

Voilà votre listener est implémenté et correctement déployé, il sera donc pris en compte lors du prochain démarrage de votre application. Histoire de tester nous vous proposons d'implémenter la jsp suivante pour vérifié qu'on utilise toujours la même instance pour la connexion.

<%@page import="java.sql.Connection"%>

<% 
Connection cnx = (Connection)application.getAttribute("myCnx");
out.println(cnx.toString());
%>