Accueil > Non classé > Java EE 6 en Scala, partie 2 : EJB 3.1

Java EE 6 en Scala, partie 2 : EJB 3.1

Introduction

La lecture de ce billet suppose celle du billet précèdent, en particulier la mise en place de l’environnement.

Le but de cette seconde partie est de s’amuser avec les EJB 3.1, en soulignant les différences avec la spécification précédente, le tout en Scala. Nos EJB seront donc des ESB (Enterprise Scala Bean), acronyme pouvant également signifier Enterprise Service Bus ou encore Encéphalopathie spongiforme bovine. On peut néanmoins espérer qu’une application JEE en Scala ne ressemble pas à un large véhicule bruyant rempli de ruminants dérangés.

Web profile et nouveautés des EJB 3.1

Web profile

Java EE 6 introduit la notion de profil : il s’agit d’un sous-ensemble de la spécification que peut choisir d’implémenter un « vendor » ( que l’on peut traduire maladroitement par « fournisseur ») par exemple JBoss ou encore Apache. Un profil intéressant est donné à titre d’exemple dans la spécification : Web profile. Il comprend : JSF, les servlet et les JSP, JPA, JTA, CDI (alias weld), et les EJB light. On parle d’EJB light car toutes les fonctionnalités ne sont pas disponibles, en particulier les MDB et les interfaces Remote.

On peut donc créer une application Java EE complète profitant des fonctionnalités des EJB sans subir le lourd packaging des ears : plus besoin de projets maven multimodules (jar d’ejb, war, ear, et parent), pour un ear contenant dix classes et trois pages JSF.

L’idée de profil est une évolution importante pour Java EE : une société peut implémenter certaines API et fournir un profil correspondant à un sous-ensemble valide et utile de la spécification, les possibilités n’étant limitées que par l’imagination au sein de la combinatoire formée par les différents modules de Java EE.

EJB 3.1

En dehors des profils, EJB 3.1 est une mise à jour mineure des EJB 3.0 :

  • Il est désormais possible de créer des EJB sessions sans avoir à déclarer une interface marquée avec @Local. C’est une évolution naturelle et attendue. Les interfaces servent à décrire un contrat commun à plusieurs implémentations interchangeables et/ou à laisser la possibilité de faire des proxy JDK. Elles sont donc inutiles pour des EJB à interface locale n’ayant qu’une seule implémentation. Extraire des interfaces qui ne font que porter une annotation pour plaire au conteneur a quelque chose de frustrant. Il est bien sûr impossible de se passer de la déclaration d’interfaces remote : si le bean est potentiellement utilisé depuis une autre JVM, on fait du RMI et on a forcement besoin d’un proxy.
  • On peut désormais utiliser les EJB dans un environnement Java SE (via une API).  C’est un point qui va plaire à ceux qui aiment les tests d’intégrations en conditions réelles : on peut tester encore plus facilement les EJB sans conteneur. Cette fonctionnalité ne sera pas traitée dans le cadre de ce billet.
  • On peut appeler des EJB sessions de manière asynchrone en utilisant l’annotation @Asynchronous, comme dans JBoss Seam.
  • Les EJB singletons ont été ajoutés, avec l’annotation @Singleton. Ces EJB sont similaires aux EJB sessions, sauf qu’il n’y a qu’une seule instance par application. La spécification EJB 3.1 ne couvre pas le clustering pour ce type d’EJB mais il est probable que la plupart des fournisseurs l’implémentent, comme ils le font pour les EJB sessions (il suffit après tout d’avoir un pool d’EJB).
  • La syntaxe des noms JNDI des EJB a été standardisée, afin d’améliorer la portabilité.

Implémentons !

EJB sans interface

Grâce à Web Profile, on peut conserver notre packaging de type war et y ajouter des EJB en scala. On peut reprendre le projet créé lors du billet précédent et y ajouter un ESB affichant un message provocateur. Il s’agit d’un EJB sans état n’implémentant pas d’interface.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  com.exemple.scalaweb

import javax.ejb.Stateless

@Stateless
class  CrazyESB {
  def meuh() = """
    _______________________
    < Java is dead         >
    -----------------------
                  \  ^__^
                   \ (oo)\_______
                     (__)\        )\/\
                          ||----w |
                          ||     ||
     "
""
}

On peut ensuite l’injecter dans une servlet avec cette bonne vieille annotation @EJB de Java EE 5 :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package  com.exemple.scalaweb

import javax.servlet.ServletException
import  javax.servlet.http.HttpServlet
import  javax.servlet.http.HttpServletRequest
import  javax.servlet.http.HttpServletResponse
import  javax.servlet.annotation.WebServlet

@WebServlet("/hello.html")
class  HelloServlet extends HttpServlet {
  @EJB
  private  var crazyESB : CrazyESB

  override def doGet(request :  HttpServletRequest,
                                response : HttpServletResponse ) {
    val html = "<html><body><pre>" + crazyESB.meuh() +  "</pre></body></html>"
    response.getOutputStream().println(html)
  }
}

Ce code est profondément similaire à ce qu’on ferait en java, à part les différences de syntaxe déjà vues précédemment. Cependant, il ne compile pas :


1
2
3
4
5
[ERROR]   ScalaWeb/src/main/scala/com/exemple
/scalaweb/HelloServlet.scala:15:  error: abstract member may not have private modifier
[INFO]   private var crazyESB : CrazyESB
[INFO]               ^
[ERROR] one error found

Scala n’initialise pas les attributs à null par défaut comme le fait java et voit notre attribut comme un membre abstrait. Un membre abstrait est un membre qui n’est pas complétement défini dans la classe où il est déclaré, et pour lequel on s’attend à ce qu’une sous classe le définisse complétement. En java, on peut définir des méthodes abstraites, Scala va plus loin et permet également de définir des attributs abstraits. Évidemment un attribut abstrait privé n’a pas beaucoup de sens.

On initialise donc l’attribut à null pour lui donner une définition complète :


1
2
  @EJB
  private  var crazyESB : CrazyESB = null

Le code compile. On déploie et là, c’est le drame :


1
2
3
4
5
6
7
8
9
10
11
12
INFO: PWC1412:  WebModule[/ScalaWeb] ServletContext.log():PWC1409: Marking servlet  com.exemple.scalaweb.HelloServlet as unavailable
ATTENTION: StandardWrapperValve[com.exemple.scalaweb.HelloServlet]: PWC1382:  Allocate exception for servlet com.exemple.scalaweb.HelloServlet
com.sun.enterprise.container.common.spi.util.InjectionException:  Error creating managed object for class  com.exemple.scalaweb.HelloServlet
 at  com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.createManagedObject(InjectionManagerImpl.java:312)
 at com.sun.enterprise.web.WebContainer.createServletInstance(WebContainer.java:700) [...] at  javax.naming.InitialContext.lookup(InitialContext.java:392)< at  com.sun.ejb.EjbNamingReferenceManagerImpl.resolveEjbReference(EjbNamingReferenceManagerImpl.java:169) ... 38 more
Caused by:  javax.naming.NameNotFoundException:  com.exemple.scalaweb.CrazyESB#com.exemple.scalaweb.CrazyESB not found
 at  com.sun.enterprise.naming.impl.TransientContext.doLookup(TransientContext.java:197)
 at  com.sun.enterprise.naming.impl.TransientContext.lookup(TransientContext.java:168)
 at  com.sun.enterprise.naming.impl.SerialContextProviderImpl.lookup(SerialContextProviderImpl.java:58)
 at  com.sun.enterprise.naming.impl.LocalSerialContextProviderImpl.lookup(LocalSerialContextProviderImpl.java:101)
 at  com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:430)
... 40 more

Le conteneur ne trouve pas notre ESB. Sommes-nous devenus fous ?

Pas encore. Jettons un oeil au bytecode généré, avec l’outil javap :


1
2
3
4
5
6
7
]$ javap  target/classes/com/exemple/scalaweb/CrazyESB
Compiled from  "CrazyEJB.scala"
public class com.exemple.scalaweb.CrazyESB extends  java.lang.Object implements scala.ScalaObject{
    public  com.raf.scalaweb.CrazyEJB();
    public java.lang.String meuh();
    public int $tag()       throws java.rmi.RemoteException;
}

Notre bean implémente une interface : c’est la méthode qu’utilise le compilateur pour que toutes les classes Scala « héritent » de ScalaObject (qui ajoute des méthodes comme « == » ou « != ») tout en restant compatibles avec Java (le code écrit en Java peut utiliser du code écrit en Scala).

Il n’est pas étonnant que le conteneur soit troublé, il cherche probablement à utiliser l’interface ScalaObject comme interface locale.

La mort dans l’âme, on se résout à déclarer une véritable interface locale et à ne pas profiter de cette alléchante nouveauté (qui fonctionne très bien en java bien sûr).

Comment déclare-t-on une interface en scala ? Eh bien en fait scala n’a pas de notion d’interface, on parle plutôt de trait. Les traits sont des interfaces, sauf qu’on peut mettre du code dedans : des méthodes, des attributs, … En fait un trait c’est exactement comme une classe sauf que :

  • Il ne peut pas prendre de paramètres de classes. En gros il est impossible de passer des arguments au constructeur.
  • Une classe peut « hériter » (on dit plutôt mix in), de plusieurs traits mais ne peut hériter que d’une seule classe.
  • Les traits sont traduits en bytecode par une interface, et le code du trait est ajouté à la classe.

Prenons un exemple pour bien expliquer le concept. Imaginez une interface Triable, que pourrait implémenter toute classe contenant des éléments implémentant Comparable. Où écrit-on l’algorithme de tri ? Celui-ci est commun à toute les implémentation de l’interface, ce serait dommage de le dupliquer. La notion de trait répond à cette problématique : on définit le tri dans le trait Triable, que l’on peut ensuite mixer dans des classes comme TreeSet.

On renomme donc notre bean en CrazyESBBean et on crée un trait CrazyEJB :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  com.exemple.scalaweb

import javax.ejb.Stateless

@Stateless
class   CrazyESBBean extends CrazyESB {
 def meuh() = """
    _______________________
    < Java is dead          >
    -----------------------
                  \  ^__^
                   \ (oo)\_______
                     (__)\        )\/\
                          ||----w |
                          ||     ||
     "
""
}

1
2
3
4
5
6
7
8
package  com.exemple.scalaweb

import javax.ejb.Local

@Local
trait  CrazyESB {
  def meuh() : String
}

Cette fois javap nous donne un résultat plus glassfish friendly :


1
2
3
4
5
6
7
]$ javap target/classes/com/exemple/scalaweb/CrazyESBBean
Compiled from "CrazyEJBBean.scala"
public class  com.exemple.scalaweb.CrazyESBBean extends java.lang.Object implements  com.exemple.scalaweb.CrazyESB,scala.ScalaObject{
    public com.exemple.scalaweb.CrazyESBBean();
    public java.lang.String meuh();
    public int  $tag()       throws java.rmi.RemoteException;
}

Notre ESB implémente deux interfaces mais comme CrazyESB est marquée avec @Local le conteneur devrait savoir que c’est celle là qu’il faut utiliser et déployer correctement.

On compile, on déploie :

Servlet utilisant un ESB et affichant un message pomélique

Servlet utilisant un ESB et affichant un message polémique

Et ça marche !

Singleton

Autre nouveauté intéressante : les EJB singletons. Comme je l’ai écrit ci-dessus il s’agit d’EJB sessions n’ayant qu’une instance par application. Leur déclaration se fait en utilisant l’annotation @Singleton plutôt que @Stateless ou @Stateful. Pour bien voir qu’il n’y a qu’une seule instance, on va faire le bon vieux coup du compteur en attribut. Notre interface comportera donc une méthode pour récupérer la valeur du compteur (count) et une méthode pour l’incrémenter (inc) :


1
2
3
4
5
6
7
8
9
10
11
12
13
package com.exemple.scalaweb

// équivaut à  "import "javax.ejb.*; en Java", * étant un identificateur valide en Scala
import javax.ejb._

@Local
trait  SingletonESB {
  // Définition d'une méthode renvoyant la valeur actuelle du compteur
  def count : Int

  // définition d'une méthode permettant d'incrémenter le compteur
  def inc()
}

L’implémentation porte l’annotation :


1
2
3
4
5
6
7
8
9
10
11
package com.exemple.scalaweb

import javax.ejb._

@Singleton
class SingletonESBBean extends SingletonESB {
  var count : Int = 0
  def inc() {
    count = count + 1
  }
}

Je vois déjà des froncements de sourcils : on déclare l’attribut, on implémente la méthode inc(), mais où est passée la méthode count ? J’illustre ici le principe d’accès uniforme inspiré par le langage Eiffel, qui stipule que le code client ne devrait pas être impacté par la décision d’implémenter une propriété par un attribut ou par une méthode. C’est pour suivre ce principe que les accesseurs générés par scala pour un attribut portent le nom de cet attribut (au lieu de getXxx) et s’utilisent sans ajouter de parenthèses. Et c’est également pourquoi on peut en scala redéfinir une méthode par un attribut ou un attribut par une méthode (la classe non ?).

On peut ensuite ajouter la servlet triviale qui utilise le singleton :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.exemple.scalaweb

import javax.servlet.ServletException
import javax.ejb.EJB
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.annotation.WebServlet

@WebServlet("/hello2.html")
class OtherServlet extends HttpServlet {

  @EJB
  private var  singletonESB : SingletonESB = null

  override def doGet(request : HttpServletRequest,  response : HttpServletResponse ) {
    var html =  "<html><body><p>" + singletonESB.count  +  "</p></body></html>"
    singletonESB.inc()
    response.getOutputStream().println(html)
  }
}

Compiler, déployer et tester :

Utilisation d'un ESB singleton

Utilisation d'un ESB singleton

Bien sûr les servlets sont également des singletons. Pour sa tranquillité d’esprit le lecteur zélé pourra créer une seconde servlet similaire et vérifier que l’état du singleton est bien partagé entre les deux.

Asynchronicité

L’asynchronicité est une fonctionnalité bien pratique quand une application fait des traitements un peu longs et se veut tout de même réactive. Cette technique est massivement utilisée par des petites boîtes aux sites peu appréciés comme Google ou Amazon, elle est également au cœur du développement en Flex. L’idée est d’exécuter une action sans en attendre la réponse. La différence entre le synchrone et l’asynchrone est un peu la même qu’entre une messagerie instantanée et un courriel : dans le premier cas on pose une question et on attend devant la fenêtre jusqu’à ce que réponse s’en suive, dans le second on envoie un message et on va boire un café ou danser la scala.

Un exemple pour bien comprendre l’utilité : imaginons que vous ayiez à implémenter une fonctionnalité « demander une augmentation » dans une application de gestion du personnel. L’application communique avec un service distant au siège de l’entreprise aux USA, service mettant un temps excessif à répondre (plusieurs secondes). Pour compliquer on fait plusieurs essais avec des pourcentages d’augmentation différents (15%, puis 10% puis 5%). Il n’est pas envisageable que l’utilisateur attende vingt secondes devant un écran blanc ou une image qui tourne, la respectabilité de l’application est en cause : tout doit se faire instantanément. L’idée est donc d’afficher un message du style « votre demande est à l’étude, vous recevrez un courriel vous indiquant le résultat » et de lancer le traitement en arrière plan.

Il est désormais possible en Java EE de faire des appels de méthodes asynchrones très simplement, avec l’annotation @Asynchronous :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package  com.exemple.scalaweb

import javax.ejb._

@Stateless
class AsyncBean extends AsyncESB {

  @Asynchronous
  def stillAlive() {
    // traitement long
    (1 to 4).foreach { (i) =>
      println("Ah")
      Thread.sleep(1000)
    }
    (1 to 2).foreach { (i) =>
      println("Staying alive")
     Thread.sleep(1000)
    }
  }
}

1
2
3
4
5
6
package  com.exemple.scalaweb
import javax.ejb._
@Local
trait AsyncESB {
  def stillAlive()
}

On crée ici à la volée des intervalles d’entiers sur lesquels on boucle avec un forEach comme vu dans le post précédent. Le traitement est volontairement allongé avec des appels à la méthode Thead.sleep. Il reste à créer du code pour appeler l’EJB :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package  com.exemple.scalaweb

import javax.servlet.ServletException

import javax.ejb.EJB
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.annotation.WebServlet

@WebServlet("/async.html")
class AsyncServlet extends HttpServlet {

  @EJB
  private var asyncESB : AsyncESB =  null

  override def  doGet(request : HttpServletRequest, response : HttpServletResponse ) {
    asyncESB.stillAlive()
    println("Right here")
    response.getOutputStream().println("<html><body></body></html>")
   }
}

Note : J’utilise un simple println pour cet exemple (au fait le System.out est implicite en Scala), mais bien entendu il faudrait utiliser un logger.

On compile, on déploie, on affiche la « page » de la servlet, et enfin on va voir les logs du serveur, pour vérifier que notre méthode a bien été exécutée de manière asynchrone (la configuration des logs de glassfish est celle par défaut, assez verbeuse) :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
[#|2010-01-30T18:19:19.274+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=27;_ThreadName=http-thread-pool-8080-(1);
|Right  here|#]
[#|2010-01-30T18:19:19.365+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Ah|#]
[#|2010-01-30T18:19:20.367+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Ah|#]
[#|2010-01-30T18:19:21.368+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Ah|#]
[#|2010-01-30T18:19:22.368+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Ah|#]
[#|2010-01-30T18:19:23.371+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Staying  alive|#]
[#|2010-01-30T18:19:24.371+0100|INFO|glassfishv3.0|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=40;_ThreadName=Ejb-Async-Thread-14;
|Staying  alive|#]

Comme on le voit, le « Right here » qui est après l’appel de la méthode de l’EJB dans la servlet est écrit avant la fin de celle-ci.

Une question se pose ici : que ce passe t’il si la méthode marquée avec @Asynchronous renvoie quelque chose ? J’ai testé pour vous : le conteneur pique une colère au déploiement :


1
Caused by: java.lang.RuntimeException: Invalid  asynchronous bean class / interface method signatures for bean  AsyncBean. beanMethod = 'public java.lang.String  com.exemple.scalaweb.AsyncBean.stillAlive()' , interface method =  'public abstract java.lang.String  com.exemple.scalaweb.AsyncESB.stillAlive()'

Le cas a été prévu.

Fin

Comme on le voit l’utilisation d’EJB en Scala n’est pas plus compliquée qu’en Java. On peut regretter d’être obligé de déclarer un trait pour chaque ESB à cause du implements scala.ScalaObject mais on peut espérer que si Scala gagne encore en popularité les implémentations des conteneurs Java EE en tiendront compte.

Prochain épisode : JPA 2.0

Références :

Share
  1. 04/02/2010 à 11:28 | #1

    Très bon article, on en apprend à la fois sur Scala et sur Java EE 6 :D

    Au niveau des traits, j’ai l’impression que ça ressemble fortement aux mixins de JavaFX, qui eux-mêmes ne semblent être que des classes abstraites permettant un héritage multiple… J’ai un peu du mal à voir la plus-value par rapport à une bonne vieille classe abstraite.

    En effet, il est dommage qu’il faille déclarer un trait uniquement pour y mettre l’annotation @Local, les conteneurs supposent donc que dès qu’un EJB implémente une interface c’est forcément une @Local ou une @Remote ?

  2. 04/02/2010 à 12:19 | #2

    Oui il s’agit du même concept.

    La différence entre les mixins (ou traits) et les classe abstraites c’est qu’on ne peut hériter que d’une seule classe abstraite, alors qu’on peut mixer plusieurs mixins dans une classe (ou une instance).

    Pour prendre un exemple bateau, Animal est une classe abstraite, et Carnivore et Bipede sont des traits.

    Je ne sait pas trop ce que les conteneurs supposent. Ils sont souvent plus fort qu’ils ne le devraient. Je sais que j’ai déjà utilisé des EJB dans mes applications seam sans mettre le @Local sur l’interface, et ça fonctionnait.

  3. 18/02/2010 à 19:00 | #3

    Une méthode asynchorne peut dans certains cas renvoyer un résultat du type java.util.concurrent.Future.
    Il est aussi possible d’annuler la méthode, vérifier si son invocation est terminée, etc.
    Cf. http://java.sun.com/javase/6/docs/api/java/util/concurrent/Future.html

    PS: merci pour l’article!

  4. Raphaël LEMAIRE
    18/02/2010 à 19:21 | #4

    Effectivement, on peut aussi renvoyer une instance de java.util.concurrent.Future. C’est dans la spec Section 4.5.2.

    Merci !

    Ça peut être plus pratique pour récupérer le résultat d’un calcul que de le stocker en base ou en mémoire dans une structure de données partagée. Attention quand même à l’utilisation de la méthode get() de Future qui est bloquante si le calcul n’est pas terminé : on perd l’avantage de l’asynchronicité.

    Ça peut être sympa pour du calcul distribué en fait !

    Pour la moyenne des applications d’entreprises par contre je pense que void sera plus courant.

  5. rameses
    16/10/2012 à 16:04 | #5

    bonjour,
    bien intéressent votre article.
    ce pendant, dans le snippet suivant:

    package com.exemple.scalaweb

    import javax.ejb._

    @Singleton
    class SingletonESBBean extends SingletonESB {
    var count : Int = 0
    def inc() {
    count = count + 1
    }
    }

    pour quoi avoir créer la classe singleton avec le mot-clé classe, alors que scala a prévu le mot-clé : “object”.

    par example, object SingletonESBBean extends SingletonESB { …. }

    merci,

  1. Pas encore de trackbacks