Nous avons vu comment intégrer GWT dans un projet Maven, nous allons maintenant nous intéresser à la communication GWT/Spring.
Définition des services
Pour communiquer avec le serveur, GWT utilise une interface de service utilisée coté client et coté serveur. Cette interface va définir les méthodes pouvant être appelées coté client par GWT, l'implémentation étant réalisée coté serveur.
Dans notre exemple, nous allons réaliser une interface UserService qui contiendra une méthode login prenant en paramètres un nom d'utilisateur et un mot de passe. En retour, elle renverra un objet de type User.
Pour commencer, nous allons créer un package service dans le package com.myjavaland.spring.gwt.
Dans ce package, nous allons ajouter notre interface service.
package com.myjavaland.spring.gwt.service;
import org.apache.catalina.User;
import com.google.gwt.user.client.rpc.RemoteService;
@RemoteServiceRelativePath("userService.rpc")
public interface UserService extends RemoteService{
public User login(String login, String password);
}
import org.apache.catalina.User;
import com.google.gwt.user.client.rpc.RemoteService;
@RemoteServiceRelativePath("userService.rpc")
public interface UserService extends RemoteService{
public User login(String login, String password);
}
L'interface étend RemoteService, cette interface ne définit aucune méthode mais va permettre à GWT de traiter cette interface comme une interface de service distant. Nous avons également utilisé l'annotation @RemoteServiceRelativePath qui prend une chaîne de caractère en argument. Cette URL va nous permettre de faire le mapping entre notre client et notre serveur. Concrètement, lorsque GWT va faire son appel à la procédure login, il va en réalité envoyer une requête HTTP en mode POST à l'URL http://server/project/userService.rpc en lui passant ses paramètres dans le corps du message HTTP. Le serveur devra alors intercepter la requête et l'envoyer au bon service.
Nous allons également définir un bean User. Cet objet sera partagé entre la partie cliente et serveur, nous allons donc placer la classe dans un package nommé com.myjavaland.spring.gwt.shared.
package com.myjavaland.spring.gwt.shared;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private String firstName;
private String lastName;
private String login;
private String password;
public User(){
}
public User(long id, String firstName, String lastName, String login,
String password) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.login = login;
this.password = password;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", firstName=" + firstName + ", lastName="
+ lastName + "]";
}
}
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private String firstName;
private String lastName;
private String login;
private String password;
public User(){
}
public User(long id, String firstName, String lastName, String login,
String password) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.login = login;
this.password = password;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", firstName=" + firstName + ", lastName="
+ lastName + "]";
}
}
Ce bean classique définit 5 attributs et leurs accesseurs associés.
Nous voyons que la classe UserService comporte une erreur : Missing asynchronous interface UserServiceAsync. Nous devons en effet créer une interface, dite asynchrone, qui reprend les méthodes de l'interface UserService, mais dont le type de retour est void et qui possède un paramètre supplémentaire de type AsyncCallBack<User>. C'est cette interface qui va nous permettre d'appeler les procédures distantes depuis la partie cliente.
GWT nous propose un outil pour créer automatiquement notre interface asynchrone, il suffit de cliquer sur l'erreur pour voir apparaître la popup suivante :
Une fois la fenêtre de création validée, nous obtenons le code suivant :
package com.myjavaland.spring.gwt.service;
import org.apache.catalina.User;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface UserServiceAsync {
void login(String login, String password, AsyncCallback<User> callback);
}
import org.apache.catalina.User;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface UserServiceAsync {
void login(String login, String password, AsyncCallback<User> callback);
}
Nous voyons notre méthode login, les 2 paramètres d'origine puis un 3ème permettant la gestion des retours d'appel distant, avec pour type générique, le type de retour de notre méthode login d'origine.
Nous allons maintenant voir comment appeler notre méthode depuis notre application cliente.
Pour cela, nous allons ouvrir le fichier Application.java présent dans le package com.myjavaland.spring.gwt.client. Dans l'article précédent, nous avions défini 2 TextBox et un Button au sein d'un DecoratorPanel. Nous allons modifier la classe comme suit :
package com.myjavaland.spring.gwt.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DecoratorPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
public class Application implements EntryPoint {
public void onModuleLoad() {
// Nommage de nos composants
final TextBox txtLogin;
final TextBox txtPassword;
final Button btConnexion;
// (1) Création d'un layout en tableau
FlexTable layout = new FlexTable();
layout.setCellSpacing(6);
FlexCellFormatter cellFormatter = layout.getFlexCellFormatter();
// (2) Ajout du titre de la page
layout.setHTML(0, 0, "Connexion");
cellFormatter.setColSpan(0, 0, 2);
cellFormatter.setHorizontalAlignment(0, 0,
HasHorizontalAlignment.ALIGN_CENTER);
// (3) Création de 2 TextBoxes pour le login et le mot de passe
layout.setHTML(1, 0, "Login");
txtLogin = new TextBox();
layout.setWidget(1, 1, txtLogin);
layout.setHTML(2, 0, "Password");
txtPassword = new TextBox();
layout.setWidget(2, 1, txtPassword);
// (4) Ajout du bouton de connexion
btConnexion = new Button("Connexion");
layout.setWidget(3, 0, btConnexion);
cellFormatter.setColSpan(3, 0, 2);
cellFormatter.setHorizontalAlignment(3, 0,
HasHorizontalAlignment.ALIGN_CENTER);
// (4b)
btConnexion.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//TODO Appel de procédure distante
}
});
// (5) Création du Panneau d'affichage et définition du layout
DecoratorPanel decPanel = new DecoratorPanel();
decPanel.setWidget(layout);
// (6) Ajout du Panneau dans la page courrante
RootPanel.get("content").add(decPanel);
}
}
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DecoratorPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
public class Application implements EntryPoint {
public void onModuleLoad() {
// Nommage de nos composants
final TextBox txtLogin;
final TextBox txtPassword;
final Button btConnexion;
// (1) Création d'un layout en tableau
FlexTable layout = new FlexTable();
layout.setCellSpacing(6);
FlexCellFormatter cellFormatter = layout.getFlexCellFormatter();
// (2) Ajout du titre de la page
layout.setHTML(0, 0, "Connexion");
cellFormatter.setColSpan(0, 0, 2);
cellFormatter.setHorizontalAlignment(0, 0,
HasHorizontalAlignment.ALIGN_CENTER);
// (3) Création de 2 TextBoxes pour le login et le mot de passe
layout.setHTML(1, 0, "Login");
txtLogin = new TextBox();
layout.setWidget(1, 1, txtLogin);
layout.setHTML(2, 0, "Password");
txtPassword = new TextBox();
layout.setWidget(2, 1, txtPassword);
// (4) Ajout du bouton de connexion
btConnexion = new Button("Connexion");
layout.setWidget(3, 0, btConnexion);
cellFormatter.setColSpan(3, 0, 2);
cellFormatter.setHorizontalAlignment(3, 0,
HasHorizontalAlignment.ALIGN_CENTER);
// (4b)
btConnexion.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//TODO Appel de procédure distante
}
});
// (5) Création du Panneau d'affichage et définition du layout
DecoratorPanel decPanel = new DecoratorPanel();
decPanel.setWidget(layout);
// (6) Ajout du Panneau dans la page courrante
RootPanel.get("content").add(decPanel);
}
}
Par rapport au code précédent, nous avons externalisé les 3 composants TextBox et Button, nous les utiliserons plus tard. Nous avons par ailleurs ajouté au repère 4b, un Handler sur le bouton. Lorsque l'utilisateur clique sur le bouton Connexion, la méthode onClick est appelée.
Il ne nous reste plus maintenant qu'à appeler notre méthode distante.
Pour comprendre le fonctionnement d'un appel à une procédure distante, nous allons étudier l'un des mécanismes fondamentaux de GWT : La liaison différée.
La liaison différée
La liaison différée (ou deferred binding) est un mécanisme puissant permettant l'instanciation d'une classe générée automatiquement à partir d'une interface ou d'une classe abstraite. Comment cela fonctionne-t-il ?
GWT propose une méthode : GWT.create. Cette méthode prend en paramètre une interface ou une classe abstraite et en fonction d'un certain nombre de règles définies dans les fichiers de configuration de modules de GWT, une instance d'une classe dérivée de l'interface ou de la classe abstraite va être créée. Dans le cas d'une procédure distante, nous allons créer un attribut userService dans la classe Application comme suit :
private UserServiceAsync userService = GWT.create(UserService.class);
Lors de la compilation (et uniquement lors de la compilation, GWT n'étant pas capable de réaliser de la génération de code JavaScript à l’exécution), GWT va regarder les règles associées à l'interface UserService et remarquer que UserService étend RemoteService. Pour celà, il se base sur le fichier /com/google/gwt/rpc/RPC.gwt.xml qui définit la règle.
<generate-with class="com.google.gwt.rpc.rebind.RpcServiceGenerator">
...
<when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService" />
...
</generate-with>Nous voyons donc que lorsqu'une interface héritant de RemoteService est appelée par la méthode GWT.Create, GWT va lui attribuer un Generator
RpcServiceGenerator. Ce generator va créer le code correspondant à une implémentation de la classe. Dans le cas d'un service distance, cela consiste à créer non pas une implémentation de UserService, mais de UserServiceAsync. La classe, pour chacune des méthodes, servira en fait à construire la requête RPC, à la transmettre au client et une fois la réponse renvoyée par le serveur, à décoder les flux de retour.
Notre attribut userService est donc une instance d'une classe générée à la compilation par GWT.
Maintenant que nous avons défini notre service distant, nous allons voir comment l'appeler.
Appel de procédures distantes
Au repère 4b, nous avons laissé une place libre pour réaliser notre appel, à la place du TODO, nous allons insérer le code suivant :
userService.login(txtLogin.getValue(), txtPassword.getValue(), new AsyncCallback<User>() {
public void onSuccess(User user) {
if(user != null){
Window.alert("Authentication Ok : " + user);
} else {
Window.alert("Wrong login or password");
}
}
public void onFailure(Throwable caught) {
Window.alert("Authentication failed - Error : " + caught.getMessage());
}
});
public void onSuccess(User user) {
if(user != null){
Window.alert("Authentication Ok : " + user);
} else {
Window.alert("Wrong login or password");
}
}
public void onFailure(Throwable caught) {
Window.alert("Authentication failed - Error : " + caught.getMessage());
}
});
Dans le code ci-dessus, nous voyons que nous avons appelé la méthode login de la classe UserServiceAsync. Nous lui passons en paramètre les textes présents dans les TextBox txtLogin et txtPassword. Le 3ème paramètre est une classe anonyme pour laquelle nous allons définir 2 méthodes imposées par l'interface AsyncCallBack onSuccess et OnFailure. Pourquoi n'avons nous pas pu, plus simplement appeler directement login de l'interface UserService comme suit :
User user = userService.login(login, password);
Simplement parce que les appels distants se font de manière asynchrone. En effet, imaginons que notre serveur soit chargé et qu'il mette plusieurs secondes à nous renvoyer un résultat. Notre navigateur sera bloqué en attendant sa réponse. L'intérêt d'utiliser des appels distants de manière asynchrone est que notre navigateur peut réaliser d'autres tâches (réaliser un calcul, mettre à jour une IHM, etc..) en attendant le retour du serveur.
Lorsque le serveur va nous retourner une réponse, 2 cas peuvent se présenter :
- Le serveur n'a pas rencontré de problème et nous renvoie la réponse, la méthode onSuccess est appelée.
- Le serveur a rencontré un problème (Exception, Erreur 404, etc...), la méthode onFailure est appelée.
Dans notre cas, la méthode onSuccess vérifie que l'utilisateur renvoyé par le serveur est différent de null pour autoriser la connexion.
Pour terminer le code de la partie cliente, il ne nous reste plus qu'à ajouter les packages service et shared comme source dans le fichier de configuration de module de notre application : Application.gwt.xml
<source path="service" /> <source path="shared" />
Grâce à ces lignes, GWT saura qu'il doit également aller chercher les classes dans les packages service et shared.
Nous venons de terminer le code de la partie client de l'application. Nous devons maintenant nous intéresser à la partie serveur.
Développement coté serveur
La première chose que nous allons faire est de créer un package com.myjavaland.spring.gwt.server.
Ce package contiendra toutes nos classes relatives aux serveurs. Dans un projet plus conséquent, il peut être utile de séparer le code client et serveur dans 2 projets distincts.
Dans un package com.myjavaland.spring.gwt.server.service, nous allons coder l'implémentation de notre service UserService.
package com.myjavaland.spring.gwt.server.service;
import com.myjavaland.spring.gwt.service.UserService;
import com.myjavaland.spring.gwt.shared.User;
public class UserServiceImpl implements UserService {
public User login(String login, String password) {
if("foo".equals(login) && "123".equals(password)){
return new User(1, "Eric", "PELIEU", "foo", "123");
}
return null;
}
}
import com.myjavaland.spring.gwt.service.UserService;
import com.myjavaland.spring.gwt.shared.User;
public class UserServiceImpl implements UserService {
public User login(String login, String password) {
if("foo".equals(login) && "123".equals(password)){
return new User(1, "Eric", "PELIEU", "foo", "123");
}
return null;
}
}
Le code ici est trivial, nous vérifions simplement que l'utilisateur se logue avec foo et 123. Sinon, nous renvoyons un utilisateur null.
La prochaine étape consiste à définir notre service comme un bean Spring. Pour cela, nous allons renommer notre fichier sample-servlet.xml en applicationContext.xml et y définir notre bean.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="userService" class="com.myjavaland.spring.gwt.server.service.UserServiceImpl" />
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="userService" class="com.myjavaland.spring.gwt.server.service.UserServiceImpl" />
</beans>
Il est important ici, de nommer notre bean de la même manière que l'URL défini dans l'interface UserService via l'annotation @RemoteServiceRelativePath. C'est en effet grâce à cette similitude que nous serons capable de faire le mapping entre un service GWT et un bean Spring.
Nous devons maintenant indiquer à notre serveur comment traiter la requête userService.rpc. Nous avons pour ça 2 possibilités :
- Définir pour chaque service dans le web.xml une servlet correspondante
- Définir une servlet unique pour chaque service et réaliser le dispatching sur cette servlet.
Nous allons mettre en oeuvre la 2nd solution.
La servlet va réaliser plusieurs actions :
- Récupérer la requête GWTRPC,
- Transformer les paramètres de la requête en objets java,
- Trouver la classe et la méthode associées à la requête parmi nos bean Spring,
- Appeler la méthode de la classe en lui passant les paramètres,
- Récupérer et encoder le résultat de l'appel,
- Envoyer le résultat encodé au client.
Nous voyons qu'il y a beaucoup de choses à faire avant de pouvoir appeler notre service. Par chance, GWT propose déjà tous les mécanismes pour décoder, appeler, encoder et transmettre une requête. Nous allons donc massivement réutiliser le code de GWT en y apportant de petites modifications afin de l'adapter à notre projet.
Pour ce faire, nous allons créer une classe dans un package nommé com.myjavaland.spring.gwt.server.adapter.gwt
package com.myjavaland.spring.gwt.server.adapter.gwt;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class GenericRPCServlet extends RemoteServiceServlet {
private static final long serialVersionUID = 1L;
public String processCall(String payload) throws SerializationException {
// First, check for possible XSRF situation
checkPermutationStrongName();
try {
// Récupération du bean associé à l'URL
String beanName = extractBeanName(getThreadLocalRequest()
.getServletPath());
// Chargement de l'applicationContext
WebApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
// Récupération du bean
Object bean = applicationContext.getBean(beanName);
// Décodage de la requête
RPCRequest rpcRequest = RPC.decodeRequest(payload, bean.getClass(),
this);
onAfterRequestDeserialized(rpcRequest);
// Appel de la méthode et encodage de la réponse
return RPC.invokeAndEncodeResponse(bean, rpcRequest.getMethod(),
rpcRequest.getParameters(),
rpcRequest.getSerializationPolicy(), rpcRequest.getFlags());
} catch (IncompatibleRemoteServiceException ex) {
log("An IncompatibleRemoteServiceException was thrown while processing this call.",
ex);
return RPC.encodeResponseForFailure(null, ex);
} catch (RpcTokenException tokenException) {
log("An RpcTokenException was thrown while processing this call.",
tokenException);
return RPC.encodeResponseForFailure(null, tokenException);
}
}
private String extractBeanName(String servletPath)
throws IncompatibleRemoteServiceException {
if (servletPath == null) {
throw new IncompatibleRemoteServiceException(
"servletPath can not be null");
}
String[] tServletPath = servletPath.split("/");
if (tServletPath.length < 2) {
throw new IncompatibleRemoteServiceException(
"The servletPath has failes. ServletPath : " + servletPath);
}
String servletName = tServletPath[tServletPath.length - 1];
return servletName.substring(0, servletName.length() - 4);
}
}
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class GenericRPCServlet extends RemoteServiceServlet {
private static final long serialVersionUID = 1L;
public String processCall(String payload) throws SerializationException {
// First, check for possible XSRF situation
checkPermutationStrongName();
try {
// Récupération du bean associé à l'URL
String beanName = extractBeanName(getThreadLocalRequest()
.getServletPath());
// Chargement de l'applicationContext
WebApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(getServletContext());
// Récupération du bean
Object bean = applicationContext.getBean(beanName);
// Décodage de la requête
RPCRequest rpcRequest = RPC.decodeRequest(payload, bean.getClass(),
this);
onAfterRequestDeserialized(rpcRequest);
// Appel de la méthode et encodage de la réponse
return RPC.invokeAndEncodeResponse(bean, rpcRequest.getMethod(),
rpcRequest.getParameters(),
rpcRequest.getSerializationPolicy(), rpcRequest.getFlags());
} catch (IncompatibleRemoteServiceException ex) {
log("An IncompatibleRemoteServiceException was thrown while processing this call.",
ex);
return RPC.encodeResponseForFailure(null, ex);
} catch (RpcTokenException tokenException) {
log("An RpcTokenException was thrown while processing this call.",
tokenException);
return RPC.encodeResponseForFailure(null, tokenException);
}
}
private String extractBeanName(String servletPath)
throws IncompatibleRemoteServiceException {
if (servletPath == null) {
throw new IncompatibleRemoteServiceException(
"servletPath can not be null");
}
String[] tServletPath = servletPath.split("/");
if (tServletPath.length < 2) {
throw new IncompatibleRemoteServiceException(
"The servletPath has failes. ServletPath : " + servletPath);
}
String servletName = tServletPath[tServletPath.length - 1];
return servletName.substring(0, servletName.length() - 4);
}
}
La classe RemoteServiceServlet est une classe GWT qui va nous fournir les mécanismes nécessaires à l’exécution de notre requête. La méthode principale de cette classe est la méthode processCall : C'est cette méthode qui, à partir des données brutes du POST (String Payload), va appeler la bonne méthode et retourner le résultat correctement encodé. Nous allons la recopier et la modifier légèrement.
RemoteServiceServlet est, à l'origine, prévue pour être héritée par les implémentations de services. Ainsi, RemoteServiceServlet se contente d'appeler les méthodes définies dans la classe qui l'hérite. Ici, nous allons récupérer la classe grâce au nom de la requête : La méthode extractBeanName va nous permettre d'obtenir le nom du bean associé à la requête. Dans notre exemple, si nous appelons http://localhost:8080/MavenWebApplication/userService.rpc, la méthode va nous renvoyer "userService". Nous n'avons alors plus qu'à rechercher dans notre contexte Spring le bean nommé userService. Ceci explique pourquoi il est impératif de nommer de manière identique nos beans et nos url de service.
Une fois notre bean récupéré, nous pouvons le passer en paramètre de la méthode invokeAndEncodeResponse et laisser le soin RemoteServiceServlet d'instancier, encoder et renvoyer le resultat.
Pour finir, nous devons modifier notre fichier web.xml pour lui faire router les appels RPC vers notre servlet.
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>sample</servlet-name>
<servlet-class>com.myjavaland.spring.gwt.server.adapter.gwt.GenericRPCServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sample</servlet-name>
<url-pattern>*.rpc</url-pattern>
</servlet-mapping>
</web-app>
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>sample</servlet-name>
<servlet-class>com.myjavaland.spring.gwt.server.adapter.gwt.GenericRPCServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sample</servlet-name>
<url-pattern>*.rpc</url-pattern>
</servlet-mapping>
</web-app>
La dernière étape consiste à ajouter gwt dans le pom.xml de notre projet. En effet, en ajoutant le plugin, nous avons ajouté les librairies nécessaires à GWT dans le classpath d'eclipse, mais une fois notre application déployée, ces librairies ne sont pas recopiées. Il suffit d'ajouter les dépendances suivante dans le pom.xml.
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.3.0</version>
<type>jar</type>
<scope>runtime</scope>
<optional>false</optional>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>2.3.0</version>
<type>jar</type>
<scope>provided</scope>
<optional>false</optional>
</dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>2.3.0</version>
<type>jar</type>
<scope>runtime</scope>
<optional>false</optional>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>2.3.0</version>
<type>jar</type>
<scope>provided</scope>
<optional>false</optional>
</dependency>
Conclusion
Il nous suffit maintenant de lancer notre serveur tomcat et notre client GWT pour s'assurer que tout fonctionne correctement.
Et voilà, nous sommes maintenant en mesure d'utiliser Spring et GWT simultanément. Cette architecture présente un certain nombre d'avantages : Nous avons désormais accès à tous les modules de Spring, nous pouvons facilement intégrer un système de persistence comme hibernate et surtout, nous disposons d'une application complètement découplée. Il est ainsi facile de modifier le programme pour utiliser notre bean userService au sein d'un webService par exemple.
Lors d'un prochain article, nous verrons comment ajouter une couche DAO à l'aide d'Hibernate à notre application. Nous verrons également comment intégrer les tests unitaires à notre application.




















