La dernière version snapshot de Guice intégre des fonctionnalités intéressantes pour les Servlets, notamment de nouveaux Builders qui viennent du projet warp-servlet et qui permettent de configurer des mappings de Servlet ou de Filter directement dans un module. Vous pouvez obtenir les sources de l’exemple décrit dans cet article ici.

Au lieu de définir nos RemoteServiceServlet dans le web.xml (ce qui est désormais le cas avec GWT 1.6), nous allons déclarer un GuiceServletContextListener et un GuiceFilter :


 1 <?xmlversion="1.0"encoding="UTF-8"?>
2 <web-app>
3
4
<!-- Default page to serve -->
5 <welcome-file-list>
6 <welcome-filecda.html</welcome-file>
7 </welcome-file-list>
8
9
<listener>
10 <listener-classcom.sfeir.server.CdaGuiceServletConfig
11 </listener-class>
12 </listener>
13
14
<filter>
15 <filter-nameguiceFilter</filter-name>
16 <filter-classcom.google.inject.servlet.GuiceFilter
17 </filter-class>
18 </filter>
19
20
<filter-mapping>
21 <filter-nameguiceFilter</filter-name>
22 <url-pattern/*</url-pattern>
23 </filter-mapping>
24
25
</web-app

GuiceServletContextListener crée une instance d’Injector avec la méthode getInjector(), nous pouvons alors définir notre module en redéfinissant cette méthode :


 1 public class CdaGuiceServletConfig extends GuiceServletContextListener
 2 {
 3     @Override
 4     protected Injector getInjector()
 5     {
 6         return Guice.createInjector(new ServletModule()
 7         {
 8             @Override
 9             protectedvoid configureServlets()
10             {
11                 serve("/cda/echo").with(EchoServiceImpl.class);
12                 serve("/cda/spreadsheet").with(SpreadSheetServiceImpl.class);
13 ...

Nous utilisons pour cela une classe anonyme qui hérite de ServletModule : les méthodes «serve» et «filter» de cette dernière nous permettent de faire l’équivalent de huit lignes de XML pour déclarer une Servlet ou un Filter en une seule ligne de Java.

«serve» fonctionne directement avec les RemoteServiceServlet de GWT (il faut néanmoins ajouter y l’annotation Singleton), par contre les scopes Session et Request de la classe ServletScopes ne peuvent pas être utilisés : nous allons devoir hériter de la classe RemoteServiceServlet pour y ajouter notre propre Scope Session et par la même occasion ajouter un champ «injector» qui contiendra l’objet Injector créé par notre classe CdaGuiceServletConfig.

Dans notre ServletModule, nous rajoutons un appel à «bind» qui associe l’interface SpreadsheetManager à une implémentation :


 1                 bind(SpreadsheetManager.class).
2 to(SpreadsheetManagerImpl.class);

Ainsi l’appel de getInstance de l’object Injector avec SpreadsheetManager.class en paramètre renverra une instance de SpreadsheetManagerImpl.


 1 @Singleton
2 public class SpreadSheetServiceImpl extends GuiceRPCServlet implements SpreadSheetService
3 {
4
5
@Override
6 public List<String> fetchSpreadsheetList()
7 {
8 SpreadsheetManager manager = injector.getInstance(SpreadsheetManager.class);
9 ...

Par défaut, l’Injector de Guice renvoie toujours une nouvelle instance. Nous pouvons également utiliser l’annotation Singleton ou dans le cas présent notre propre annotation GwtSessionScoped afin qu’une même instance soit réutilisée, par exemple le temps d’une session.


 1                 bindScope(GwtSessionScoped.class,
2 GuiceRPCServlet.SESSION);
3 bind(GoogleService.class).toProvider(
4 SpreadsheetServiceProvider.class).
5 in(GwtSessionScoped.class);

Ainsi les champs injectés de type GoogleService seront créés une première fois puis récupérés dans la session en cours. Ce mécanisme fonctionne quand l’Injector de Guice est appelé, il est donc exclu de l’utiliser sur des champs d’une RemoteServiceServlet.


 1 public class SpreadsheetManagerImpl implements SpreadsheetManager
2 {
3 @Inject
4 private GoogleService service;
5
6
@Inject
7 @Named("user")
8 private String username;
9 @Inject
10 @Named("pass")
11 private String password;
12
13
@Override
14 public void authenticate()
15 {
16 try
17 {
18 service.setUserCredentials(username,password);
19 } ...

L’annotation Named est utile pour injecter objets de même type sans avoir à créer de nouvelle annotation. La méthode statique «named» crée cette annotation pour l’utiliser dans les instructions de binding.


 1                 bindConstant().annotatedWith(named("user")).
2 to("cappelle.florent@gmail.com");
3 bindConstant().annotatedWith(named("pass")).
4 to("*************");

Finalement, Guice permet aussi d’ajouter des fonctions transverses à nos services en «bindant» un intercepteur sur un ensemble de méthodes :


 1                 bindInterceptor(subclassesOf(SpreadsheetManager.class),
2 returns(subclassesOf(List.class)),
3 new LoggingInterceptor());

Ici, LoggingInterceptor est appliqué aux classes qui héritent de SpreadsheetManager et plus particulièrement aux méthodes de ces classes dont le retour est de type List. Les classes d’interception doivent implémenter MethodInterceptor de l’API aopalliance.

Vous aurez sans doute deviné que LoggingInterceptor fait un log des appels de méthode, la sécurité et les transactions étant les autres exemples typiques d’utilisation des MethodInterceptor.

En conclusion, Google Guice constitue une alternative intéressante à Spring pour l’injection de dépendance. Dans certains cas, on pourra également utiliser les méthodes de la classe SpringIntegration afin de réutiliser une configuration XML existante.

Liens :