JAX-WS/CXF et Spring

Cette page a été rédigée il y a fort fort longtemps, et n'a pas tellement été mise à jour.

 

Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise page. Moi, si je devais résumer mon wiki aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas l'interlocuteur en face je dirais, le miroir qui vous aide à avancer. Alors ça n'est pas mon cas, comme je disais là, puisque moi au contraire, j'ai pu ; et je dis merci au wiki, je lui dis merci, je chante le wiki, je danse le wiki... je ne suis qu'amour ! Et finalement, quand des gens me disent « Mais comment fais-tu pour avoir cette humanité ? », je leur réponds très simplement que c'est ce goût de l'amour, ce goût donc qui m'a poussé aujourd'hui à entreprendre une construction logicielle... mais demain qui sait ? Peut-être simplement à me mettre au service de la communauté, à faire le don, le don de soi.

Le projet CXF

CXF est un projet Apache qui est issu de la fusion entre XFire et Celtix. Il s’agit d’une implémentation JAX-WS qui s’intègre facilement avec Spring Framework. Il est donc plutôt orienté "Contract Last", mais il permet cependant de développer suivant l’approche "Contract First".

Il implémente des standards WS et Java :

  • SOAP, WS-I Basic Profile, WS-Security…​

  • JAX-WS, JAX-WSA, JSR-181, SAAJ

Par ailleurs, il propose plusieurs types de transport (HTTP, JMS…​), ainsi qu’un choix entre plusieurs frameworks de Data Binding (JAXB, Aegis…​).

Dans cet article, nous traiterons uniquement de l’approche "Contract Last", i.e. le fichier WSDL sera généré à partir des classes Java annotées.

Principe de communication client / service

CXFSpring1.png

Processus de développement

Partie serveur

Pour écrire la partie serveur du service web, les étapes de développement sont les suivantes :

  • Fournir une interface + une classe d’implémentation qui seront annotées avec les annotations de l’API JAX-WS

  • Configurer Spring pour déclarer le bean de service web

  • Configurer la Servlet CXF dans le fichier web.xml

L’interface Java du service doit être annotée avec @WebService :

@WebService
public interface CatalogueService {

  Produit findProduitById(Long id);
  ...
}

Cette configuration peut être enrichie afin de contrôler plus précisément les éléments du fichier WSDL qui sera généré, notamment à l’aide des attributs de @WebService et de l’annotation @WebMethod :

@WebService(name="Catalogue",
            targetNamespace="http://www.librairie.org")
public interface CatalogueService {

  @WebMethod(operationName="getProduit")
  Produit findProduitById(@WebParam(name="id") Long id);
  ...
}

La classe d’implémentation doit pour sa part être annotée avec @WebService de façon à préciser l’interface qui définit le service web :

@WebService(endpointInterface="org.librairie.service.CatalogueService")
public class CatalogueServiceImpl implements CatalogueService {
  private ProduitDao produitDao;

  @Override
  public Produit findProduitById(Long id) {
    Produit produit = produitDao.findById(id);
    return produit;
  }
  ...
}

Au niveau de la configuration Spring, il faut :

  • importer les fichiers d’application context de CXF

  • déclarer le bean de service web avec le schéma jaxws fourni par CXF

  • déclarer les dépendances du bean de service web

Voici donc le fichier d’application context pour notre service web :

<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:jaxws="http://cxf.apache.org/jaxws"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                              http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd
                              http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

  <bean id="catalogueService"
        class="org.librairie.service.CatalogueServiceImpl">
    <property name="produitDao" ref="produitDao"/>
  </bean>

  <jaxws:endpoint id="catalogueServiceEndpoint"
                  implementor="#catalogueService"
                  address="/CatalogueService" />
</beans>

Dans l’exemple ci-dessus, <jaxws:endpoint> permet de créer le bean de web service avec l’identifiant catalogueServiceEndpoint. L’attribut implementor est utilisé pour injecter un bean Spring dans le bean de web service. A noter que le nom du bean doit être préfixé avec le caractère #.

On configure enfin le fichier web.xml afin de déclarer la servlet responsable du chargement de l’application context et du traitement des requêtes HTTP destinées aux endpoints :

<servlet>
  <servlet-name>CXFServlet</servlet-name>
  <servlet-class>
    org.apache.cxf.transport.servlet.CXFServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>CXFServlet</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Remarque : Si le fichier Spring est nommé cxf.xml et placé dans le classpath, alors la servlet CXF charge automatiquement l’application context. Sinon, il faut effectuer le chargement de l’application context via le context loader listener de Spring :

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext-cxf.xml</param-value>
</context-param>

<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

Partie client

Côté client, deux approches sont possibles :

  • Si l’interface Java du service est disponible, il suffit de créer un Factory Bean JAX-WS au niveau de la configuration Spring

  • Si on dispose uniquement du fichier WSDL, un outil permet de générer des stubs à partir du fichier WSDL

Quand on dispose de l’interface Java du service, la configuration Spring du bean client est effectué à l’aide d’un bean JaxWsProxyFactoryBean fourni par CXF :

<bean id="catalogue"
      class="org.librairie.service.CatalogueService"
      factory-bean="clientFactory" factory-method="create"/>

<bean id="clientFactory"
      class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
  <property name="serviceClass"
            value="org.librairie.service.CatalogueService"/>
  <property name="address"
            value="http://server/myapp/CatalogueService"/>
</bean>

Pour invoquer le service web depuis le code Java, il suffit alors d’instancier le conteneur Spring et de récupérer le bean client :

String[] paths = {"applicationContext-cxf-client.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(paths);

CatalogueService service = (CatalogueService)ctx.getBean("catalogue");
Produit produit = service.findProduitById(3);