JAX-WS/CXF et Spring

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 :

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 :

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 :

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 :

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);