dimanche 24 janvier 2010

Spring 3.0, @ManagedBean, @Named, @Inject, @Qualifier, Provider (JSR-330)

Dans un de mes précédents articles (http://bgaillard.blogspot.com/2010/01/sortie-de-spring-30.html) je parlais de la sortie de Spring 3.0, notamment du support partiel de la JSR-330.

Je vois dans cette JSR un premier pas vers la standardisation d'annotations Java pour l'injection de dépendances. Je détaillerai ici le fonctionnement des annotations @Inject, @Named et @Qualifier ainsi que l'interface Provider de la JSR-330. L'objectif, au final, étant de disposer d'un code compatible avec Java EE 6 j'expliquerai aussi le fonctionnement de l'annotation @ManagedBean.

Les annotations @Scope et @Singleton n'étant actuellement pas supportées par Spring 3.0 je ne les détaillerai pas dans cet article, elle feront l'objet d'un prochain article relatif à Guice

La compréhension de cet article nécessite une connaissance minimum de Spring (1, 2 ou 3) et de l'injection de dépendance. Si vous ne connaissez pas encore l'injection de dépendances (IoC pour Inversion Of Control ou DI pour Dependency Injection) le meilleur article que je puisse vous conseiller est celui de Martin Fowler situé à cette adresse: http://martinfowler.com/articles/injection.html.  

@ManagedBean
La JSR-316 défini les Managed Beans comme étant des objets gérés par un conteneur, ces objets doivent remplir un certains nombre de pré requis. Dans le cas de Spring le conteneur est évidemment le conteneur d'inversion de contrôle du framework. Les Managed Beans supportent un ensemble de services basiques tels que l'injections de ressources, des intercepteurs (JSR-316, Interceptors 1.1) et des fonctions de callback branchées sur certaines phases du cycle de vie des beans.

La spécification définie un modèle de composant basique pour les Managed Beans, ce modèle pourra être altéré dans d'autres spécifications ou certaines implémentations. Par exemple la spécification indique qu'un bean doit être déclaré via l'annotation @ManagedBean, avec Spring il est aussi possible de le faire via des instructions XML.

Voici un exemple de déclaration de Managed Bean.

@ManagedBean
  public class Bean {}

Ce qui est équivalent à la déclaration XML Spring suivante.


Un Managed Bean ne doit pas être une classe finale, abstraite ou une classe imbriquée non statique.

Par défaut le nom du bean dans le conteneur Spring sera le nom de la classe avec une lettre minuscule pour la première lettre. Il est possible de changer le nom du bean de cette manière.

@ManagedBean("autreNom")
  public class Bean {}

Deux fonctions de callback peuvent être définies.
  • @PostConstruct permet d'annoter une fonction qui sera appelée juste après la construction du Managed Bean.
  • @PreDestroy permet d'annoter une fonction qui sera appelée juste avant la destruction du Managed Bean.

Par exemple le code suivant:
@ManagedBean("autreNom")
  public class Bean {
    private Bean() {
      System.out.println("Construction...")
    }
 
    @PostConstruct
    private void postConstruct() {
      System.out.println("Post construction...");
    }
 
    @PreDestroy
    private void preDestroy() {
      System.out.println("Pre destruction...");
    }
  }

donnera la sortie suivante:
Construction...
Post construction...
Pre destruction...

Attention, si vous utilisez l'annotation @PreDestroy dans une application Java classique (non Web) la fonction suivante doit être appelée sur votre Application Context Spring.

((AbstractApplicationContext)applicationContext).registerShutdownHook();



JSRs: Managed Bean 1.0 Specification (JSR-316)
Javadoc:http://java.sun.com/javaee/6/docs/api/javax/annotation/ManagedBean.html
Package Java: javax.annotation
Dépendance Maven:

    glassfish-repository
    Java.net Repository for Glassfish
    http://download.java.net/maven/glassfish
  


    org.glassfish
    javax.annotation
    3.0
  

Téléchargement librairie: http://download.java.net/maven/glassfish/org/glassfish/javax.annotation/3.0/


@Inject

@Named

Provider


Cette article sera complété dans les prochains jours.

samedi 2 janvier 2010

Sortie de Spring 3.0

Les nouveautées
La version 3.0 GA (.RELEASE) du framework Spring vient de sortir, au menu voici quelques unes des nouveautés les plus importantes (ces indications proviennent principalement du blog http://blog.springsource.com/2009/12/16/spring-framework-3-0-goes-ga) :
  • Un réécriture complète du framework en tirant partie des spécificités de Java 5, notamment les generics et listes d'arguments variables (varargs).
  • Un support partiel de Java EE 6 (JSR-316) en terme d'environnement d'exécution mais aussi le support de la nouvelle version de JPA 2.0 (JSR-220), un support de l'annotation @ManagedBean (JSR-250) pour le scanning des composants et le support des annotations @Inject, @Named, @Qualifier et de l'interface Provider (JSR-330) pour l'injection de dépendances.
  •  L'arrivée d'un nouveau langage d'expression à utiliser dans les fichiers de configuration XML Spring ou dans les annotations. Spring Expression Language (SpEL).
  • L'apparition des annotation @Configuration et @Bean, héritages de JavaConfig.
  • L'apparition d'une nouvelle annotation @Value qui permet l'injection de valeurs de configuration statiques ou dynamiques.
  • Possibilité de créer rapidement des annotations raccourcies, par exemple créer une seule annotation qui va en remplacer plusieurs.
  • Un modèle déclaratif de validation à base d'annotations qui reprend les principe de l'API Hibernate Validator 4.0. (JSR-303).
  • Un support amélioré du binding et de la validation dans Spring MVC, le formatage peut être supporté via des annotations d'une manière similaire à la JSR-303.
  • Support de REST, utilisation facilitée dans Spring MVC, une classe RestTemplate pour faciliter la création de clients REST.
  • Support complet des portlets 2.0.
  • Le mapping Objet / XML (OXM), héritage de Spring Web Services.
  • De nouvelles fonctionnalités pour la planification de tâches.
  • Une documentation améliorée.
Ici je n'ai fais qu'un tour rapide des nouvelles fonctionnalités de Spring 3.0, j'en ai sans doute oublié certaines donc n'hésitez pas à poster !
    Liens relatifs à la sortie de Spring 3.0
    http://www.springsource.org/node/2271
    http://www.theserverside.com/news/thread.tss?thread_id=58985

    Téléchargement du framework
    http://www.springsource.com/products/spring-community-download

    Documentation
    HTML (plusieurs pages) : http://static.springsource.org/spring/docs/3.0.x/reference/html/
    HTML (une seule page) : http://static.springsource.org/spring/docs/3.0.x/reference/htmlsingle/spring-framework-reference.html
    PDF : http://static.springsource.org/spring/docs/3.0.x/reference/pdf/spring-framework-reference.pdf

    mercredi 23 décembre 2009

    Transactions automatiques avec Zend

    Dans ce post j'explique comment rendre un service Zend transactionnel sans alourdir votre code, c'est à dire sans avoir à démarrer / commiter vos transactions manuellement.

    J'entends par service une classe issue du modèle Service / DAO / modèle de données, ce modèle a été détaillé dans un précédent post : Concevoir une application Zend selon le modèle Service / DAO / Modèle de données. Dans ce post je montrais un exemple de service très simple, je propose de repartir de la classe Service_User qui correspond à un service de gestion d'utilisateurs dans une application Zend / PHP.

    Voici cette classe:

    // File 'application/services/Service.php'
    
    /**
     * Service used to manage Model_Users.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Service_User implements Service_Service {
    
      /**
       * DAO used to manage the persistence for Model_Users entities.
       *
       * @var Dao_User
       */
       private $userDao;
    
      /**
       * Constructs a new Service_User.
       *
       * @return Service_User
       */
      public function __construct() {
        $this->userDao = new Dao_User();
      }
    
      /**
       * Saves a Model_User in the system.
       *
       * @return Model_User $user the user to save in the system.
       */
      public function save(Model_Account $user) {
        $this->userDao->saveOrUpdate($user);
      }
    
      /**
       * Lists all the Model_Users registered in the system.
       *
       * @return Array An array of Model_Users which represents 
       *               all the registered Model_Users.
       */
      public function listAll() {
        return $this->userDao->listAll();
      }
    }
    

    Cette classe de service comporte 3 fonctions, vous pouvez remarquer qu'aucune de ces fonctions n'utilise de code relatif aux transactions.

    L'astuce que je propose pour rendre transactionnelles ces 3 fonctions est de créer un service de wrapping Service_TransactionalService qui se chargera de démarrer / commiter / rollbacker automatiquement les transactions.

    Voici cette classe de wrapping.

    /**
     * Service which wraps an other one to add transaction around 
     * each method of the wrapped service.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@hotmail.com)
     */
    class Service_TransactionalService implements Service_Service {
    
      /**
       * The service to make transactional.
       *
       * @var Service
       */
      private $service;
    
      /**
       * The Zend Db Adapter.
       *
       * @var Zend_Db_Adapter
       */
      private $db;
    
      /**
       * Constructs a new Service_TransactionalService by wrapping 
       * an other one.
       *
       * @param $service the service to wrap.
       * @return Service_TransactionalService
       */
      function __construct(Service_Service $service) {
        $this->service = $service;
        $this->db = Zend_Registry::get('db');
      }
    
      /**
       * Wraps a service call around a transaction.
       *
       * @param String $name name of the function to call.
       * @param Array $arguments arguments to pass to the function.
       * @return Object return of the called function.
       */
      function __call($name, $arguments) {
    
        // -- Starts a transaction
        $this->db->beginTransaction();
    
        try {
       
          $ret = call_user_func_array(
                     array($this->service, $name), 
                     $arguments);
      
          // -- Commit the transaction
          $this->db->commit();
     
          return $ret;
    
        } catch (Exception $e) {
       
          // -- Rollback the transaction
          $this->db->rollBack();
          echo $e->getMessage();
        }
      }
    }
    

    Toute la magie de cette classe repose dans l'utilisation de la méthode PHP __call(), cette méthode est appelée à chaque fois que l'on appelle une méthode inexistante sur un objet.

    Dans notre cas nous pouvons appeler 3 méthodes sur un objet de type Service_TransactionnalService, par exemple, pour un appel à listAll() :

    $transactionnalService = new Service_TransactionnalService(
        new Service_UserService());
    
    $transactionnalService->listAll();
    

    L'appel à listAll() sur $transactionnalService équivaut à avoir le code suivant dans la méthode listAll() de la classe de service Service_User.

    /**
      * Lists all the Model_Users registered in the system.
      *
      * @return Array An array of Model_Users which represents 
      *               all the registered Model_Users.
      */
    public function listAll() {
    
      // -- Starts a transaction
      $this->db->beginTransaction();
    
      try {
       
        $ret = $this->userDao->listAll();
    
        // -- Commit the transaction
        $this->db->commit();
     
        return $ret;
    
      } catch (Exception $e) {
       
        // -- Rollback the transaction
        $this->db->rollBack();
        echo $e->getMessage();
      }
    }
    

    Voilà, nous avons réussi à wrapper tous les appels de méthodes de nos services dans des transactions. Dans un de mes prochains post j'expliquerai comment accéder à vos services transactionnels de manière simple au sein de votre application afin d'éviter d'avoir des instanciations à la classe Service_TransactionnalService partout ou vous avez besoin d'un service transactionnel.

    mardi 22 décembre 2009

    Architecture Service / DAO / Modèle de données avec Zend

    Les architectures des applications Java EE sur lesquelles j'ai travaillé étaient la plupart du temps basées sur une couche de services métiers transactionnels (Spring ou EJB3), une couche de DAO (Spring / Hibernate ou EJB3 / JPA) et une couche modèle de données basée sur des POJOs (Plain Old Java Objects).

    L'objectif de ce post est de montrer comment utiliser une architecture de type Service / DAO / modèle de données avec le framwork Zend.

    La documentation du framework Zend ne pousse pas vraiment le lecteur à mettre en place de telles architectures mais nous allons voir qu'il est tout à fait possible de le faire de façon simple.

    Je n'expliquerai pas ici comment créer la hiérarchie de répertoires d'un projet Zend, veuillez vous référer à la documentation du framework Zend (http://framework.zend.com/manual/en/) ou au Quickstart (http://framework.zend.com/docs/quickstart) pour plus d'informations à ce sujet.


    En Java la répartition des classes de services, de DAOs et du modèle de données se fait en créant des packages différents pour chacune des couches. Les classes sont ensuite suffixées, par exemple une interface correspondant à un service utilisateur pourrait s'appeler UserService, un DAO UserDao et une entité Hibernate User.

    PHP et le framework Zend n'offrent pas de notion de packages, mais, en appliquant quelques modifications à la structure des répertoires d'un projet Zend et à l'Autoloader nous pouvons arriver à avoir quelque chose de similaire.


    Structure des répertoires

    Je propose de placer les classes de services dans un répertoire 'services', les classes de DAO dans un répertoire 'daos' et les classes du modèle de données dans un répertoire 'models'.

    Vous devez disposer des répertoires suivants dans votre application Zend:

    • Un répertoire 'application/services'
    • Un répertoire 'application/daos'
    • Un répertoire 'application/models'

    Surcharge de l'Autoloader Zend

    Une fois les répertoires créés nous allons devoir surcharger l'Autoloader Zend de façon à pouvoir charger automatiquement les classes de services, de DAOs et du modèle de données.

    De base l'Autoloder Zend permet de gérer les répertoires 'applications/services' et 'application/models' mais pas le répertoire 'application/daos'.

    Pour permettre la gestion de cette structure de répertoires la solution consiste à surcharger la classe Zend_Application_Module_Autoloader de la manière suivante.

    // File 'application/Bootstrap.php'
    
    /**
     * Custom Zend AutoLoader used to allow use of a 'daos' 
     * directory which contains 'Dao_' prefixed classes.
     * 
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class CustomAutoLoader 
     extends Zend_Application_Module_Autoloader {
    
      /**
       * Initialize default resource types for module resource 
       * classes
       *
       * @return void
       */
      public function initDefaultResourceTypes() {
        $basePath = $this->getBasePath();
        $this->addResourceTypes(array(
          'dbtable' => array(
            'namespace' => 'Model_DbTable',
            'path' => 'models/DbTable',
          ),
          'form' => array(
            'namespace' => 'Form',
            'path' => 'forms',
          ),
          'model' => array(
            'namespace' => 'Model',
            'path' => 'models',
          ),
          'plugin' => array(
            'namespace' => 'Plugin',
            'path' => 'plugins',
          ),
          'dao' => array(
            'namespace' => 'Dao',
            'path' => 'daos'
          ),
          'service' => array(
            'namespace' => 'Service',
            'path' => 'services',
          ),
          'viewhelper' => array(
            'namespace' => 'View_Helper',
            'path' => 'views/helpers',
          ),
          'viewfilter' => array(
            'namespace' => 'View_Filter',
            'path' => 'views/filters',
          ),
        ));
        $this->setDefaultResourceType('model');
      }
    }
    

    Cet Autoloder permet de nommer les classes de la manière suivante:
    • Les classes de services sont préfixées par 'Service_', par exemple 'Service_User'. Les classes de services sont placées dans le répertoire 'application/services' du projet Zend.
    • Les classes de DAOs sont préfixées par 'Dao_', par exemple 'Dao_User'. Les classes de DAOs sont placées dans le répertoire 'application/daos' du projet Zend.
    • Les classes du modèle de données sont préfixées par 'Model_', par exemple 'Model_User'. Les classes du modèle de données sont placées dans le répertoire 'application/models' du projet Zend.

    L'Autoloader est ensuite utilisé de manière classique dans votre classe de Bootstrap.

    // File 'application/Bootstrap.php'
    
    /**
     * Zend Bootstrap class.
     * 
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { 
     
      /**
       * Autoload initialization function.
       */
      protected function _initAutoload() {
        $autoloader = new CustomAutoloader(array(
          'namespace' => '',
          'basePath' => dirname(__FILE__),
        ));
    
        return $autoloader;
      }
    }
    


    Exemple de classe de service
    Les classes de services doivent être placées dans le répertoire 'application/services' et disposer de la convention de nommage suivante:
    • Le nom du fichier PHP doit porter le nom du service sans le préfixe 'Service_', par exemple la classe 'Service_User' sera dans le fichier 'application/services/User.php'.
    • Le nom de la classe est toujours préfixée par 'Service_', par exemple 'Service_User'.

    Voici un exemple de service utilisateur.

    // File 'application/services/User.php'
    
    /**
     * Service used to manage Model_Users.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Service_User implements Service_Service {
    
      /**
       * DAO used to manage the persistence for Model_Users entities.
       *
       * @var Dao_User
       */
       private $userDao;
    
      /**
       * Constructs a new Service_User.
       *
       * @return Service_User
       */
      public function __construct() {
        $this->userDao = new Dao_User();
      }
    
      /**
       * Saves a Model_User in the system.
       *
       * @return Model_User $user the user to save in the system.
       */
      public function save(Model_Account $user) {
        $this->userDao->saveOrUpdate($user);
      }
    
      /**
       * Lists all the Model_Users registered in the system.
       *
       * @return Array An array of Model_Users which represents 
       *               all the registered Model_Users.
       */
      public function listAll() {
        return $this->userDao->listAll();
      }
    }
    


    Exemple de classe de DAO
    Les classes de DAO doivent être placées dans le répertoire 'application/daos' et disposer de la convention de nommage suivante:
    • Le nom du fichier PHP doit porter le nom du DAO sans le préfixe 'Dao_', par exemple la classe 'Dao_User' sera dans le fichier 'application/daos/User.php'
    • Le nom de la classe est toujours préfixée par 'Dao_', par exemple 'Dao_User'

    Voici un exemple de DAO utilisateur.

    // File 'application/daos/User.php'
    
    /**
     * DAO used to manage Model_User business entities.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Dao_User extends Dao_AbstractDao {
    
      /**
       * Constructs a new Dao_User.
       *
       * @return Dao_User
       */
      public function __construct() {
        parent::__construct(new Model_DbTable_Account());
      }
    
      /**
       * Saves a new Model_User in database or updates an existing 
       * one.
       *
       * @param Model_User $user the user to save or update.
       */
      public function saveOrUpdate(Model_User $user) { 
        ...
      }
    
      /**
       * Finds a Model_User using a technical identifier.
       * 
       * @param Integer $id The technical identifier used to find 
       *                    the Model_User.
       * 
       * @return Model_User The found Model_User or null 
       *                    if no Model_User having an identifier
       *                    equals to $id has been found in the 
       *                    system.
       */
      public function find($id) {
        ...
      }
    
      /**
       * Lists all the Model_Users registered in the system.
       * 
       * @return Array An array of Model_User representing all the 
       *               Model_User registered in the system.
       */
      public function listAll() {
        ...
      }
    }
    

    Exemple de classe du modèle de données
    Les classes du modèle de données doivent être placées dans le répertoire 'application/models' et disposer de la convention de nommage suivante:
    • Le nom du fichier PHP doit porter le nom de l'entité sans le préfixe 'Model_', par exemple la classe 'Model_User' sera dans le fichier 'application/models/User.php'
    • Le nom de la classe est toujours préfixée par 'Model_', par exemple 'Model_User'

    Voici un exemple d'entité utilisateur.

    // File 'application/models/User.php'
    
    /**
     * Business entity for a user.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Model_User extends Model_AbstractEntity {
    
      /**
       * The user name.
       *
       * @var String
       */
      private $name;
    }
    

    La classe Zend Db_Table associée pourrait être la suivante.

    // File 'application/models/DbTable/User.php'
    
    /**
     * Zend Db Table used to map the Model_User class 
     * to the users table.
     *
     * @author Baptiste GAILLARD (baptiste.gaillard@gmail.com)
     */
    class Model_DbTable_User extends Model_DbTable_AbstractTable {
    
      /**
       * The table name.
       *
       * @var String
       */
       protected $_name = 'users';
    } 
    

    Nous avons vu dans cet article que la conception d'une application Zend basée sur une architecture logicielle Service / DAO / Modèle de données est très simple à mettre en place.

    Cette architecture a évidemment de nombreux avantages, notamment une séparation claire des responsabilités en 3 couches bien définies.

    Enfin, n'hésitez pas à apporter votre avis, remarques ou critique à propos de cet article. Je n'ai trouvé que peu d'informations sur internet abordant cette architecture Service / DAO / Modèle de données en PHP. Je pense que quelques échanges sur le sujet et un partage des expériences seraient les bienvenus.