Apache CXF REST en Spring

January 12th, 2013 | 10 min read | Apache CXF, JAX-RS, Maven, Spring, Spring MVC, Web, Web services

Ik heb in een van de eerste tutorials rond Java al eens gesproken over een JAX-RS implementatie, namelijk RESTEasy. In deze tutorial ga ik dezelfde applicatie schrijven maar dan met een andere JAX-RS implementatie, namelijk die van Apache.

Ik ga voor de verandering ook eens gebruik maken van een JSON provider (Jackson)  in plaats van een XML provider (JAXB).

Heads up!

In deze tutorial wordt met regelmaat verwezen naar mijn vorige Java tutorials. Het is dus handig dat je deze (zeker de eerste) eens doorneemt.

Project opzetten

Omdat dit project eigenlijk een variant gaat zijn op het RESTEasy project, is het handig dat je een kopie neemt van resteasy-example en het project (en de deelprojecten) hernoemt.

Vergeet tevens niet om als je het project hernoemt ook de Maven configuratie te wijzigen (er wordt ook verwezen naar de parent/child-projecten vanuit de POM, deze moet je dus ook wijzigen).
In het client- en service-project is er ook een dependency naar de core die je moet hernoemen.

Indien je daarmee klaar bent kan je er ook voor kiezen om de verschillende subprojecten als project zichtbaar te maken in Eclipse.
Klik hiervoor op File, Import en kies dan voor Existing Maven Projects.

import-maven

Kies als Root directory voor de directory van je parent module, als je alles goed gewijzigd hebt zouden normaal gezien de drie child modules aanvinkbaar moeten zijn (indien dat niet zo is dan overlapt er nog een gegeven met een ander project).

import-maven-2

Vink alles aan en klik op Finish.

Maven configuratie

Een van de eerste stappen die we moeten zetten is het vervangen van de RESTEasy dependencies door die van Apache CXF. Open daarvoor de Maven POM van het parent-project en verwijder de RESTEasy dependencies en vervang deze door:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxrs</artifactId>
    <version>${cxf.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>${cxf.version}</version>
</dependency>

We hebben tevens een provider nodig die objecten kan omzetten naar JSON. Hiervoor gebruiken we Jackson:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>${jackson.version}</version>
</dependency>

Natuurlijk heb je ook nog de versienummers nodig die je aan de

<properties>

toevoegt:

<cxf.version>2.7.1</cxf.version>
<jackson.version>1.9.0</jackson.version>

Daarmee heb je de volledige Maven-configuratie. Zowel de service als de client krijgen de dependencies vanuit het parent project.

Web descriptor

Meestal gebruiken we de Spring dispatcher (die requests koppelt met de juiste methode uit de Spring controllers), maar in de service is het net iets anders. Hier moet namelijk CXF ervoor zorgen dat de juiste interactie gebeurt en niet Spring.

Open de web descriptor

web.xml

uit

src/main/webapp/WEB-INF

in je service-project en wijzig de code door:

<!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>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

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

    <servlet-mapping>
        <servlet-name>cxf-servlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

Verder zijn er geen wijzigingen aan de web-descriptors.

Spring configuratie

In de Spring-configuratie wijzigt er ook vanalles. In het service-project zal je bijvoorbeeld de nodige zaken moeten configureren om je REST service te maken (en te koppelen aan de Jackson JSON provider), in het client-project zal je dan weer een client moeten configureren.

Service Spring configuratie

Open

applicationContext.xml

uit het service-project (

src/main/webapp/WEB-INF

) en vervang de bestaande code door:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs" 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.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd"
    default-autowire="byName">

    <context:component-scan base-package="be.g00glen00b.controller" />
    <context:annotation-config />

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

    <jaxrs:server id="restContainer" address="/">
        <jaxrs:serviceBeans>
            <ref bean="restController" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
        </jaxrs:providers>
    </jaxrs:server>
</beans>

Zoals je kan zien is dit vrij verschillend ten opzichte van RESTEasy, doch loopt het redelijk parallel. Net zoals bij RESTEasy zit er een heel stuk configuratie verborgen achter de schermen en moet deze geïmporteerd worden via XML bestanden (in dit geval

cxf.xml

en

cxf-servlet.xml

).

Daarnaast wordt er ook een container geconfigureerd die een lijst van JAX-RS annotated service beans ontvangt en natuurlijk ook nog providers die de  objecten zal omzetten naar een markup language (JSON met Jackson en XML met JAXB).

Client Spring configuratie

Open

springmvc-servlet.xml

die je kan terug vinden in

src/main/resources

in het client-project en vervang de code door:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
    default-autowire="byName">

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="restClientFactory" class="org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean">
        <property name="provider">
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
        </property>
        <property name="address">
            <value>http://localhost:8080/cxf-example-service/api/</value>
        </property>
        <property name="serviceClass">
            <value>be.g00glen00b.rest.RESTClient</value>
        </property>
    </bean>
    <bean id="restClient" class="be.g00glen00b.rest.RESTClient"
        factory-bean="restClientFactory" factory-method="create" />

    <context:component-scan base-package="be.g00glen00b.controller" />
    <context:annotation-config />
</beans>

Zoals je kan zien is er ook hier veel veranderd. Het voornaamste wat hier gebeurt is dat we (zoals bij RESTEasy) een REST client maken door een proxy factory aan te roepen.

In tegenstelling tot RESTEasy ga ik dit eens doen via Spring beans. Uit de documentatie van CXF zal je kunnen nalezen dat ze gebruik maken van een

<jaxrs:client>

notatie, maar ik heb nog niet veel werkende voorbeelden hiervan gezien op het internet en qua syntax lijkt deze notatie me net iets interessanter.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
    default-autowire="byName">

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="restClientFactory" class="org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean">
        <property name="provider">
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
        </property>
        <property name="address">
            <value>http://localhost:8080/cxf-example-service/api/</value>
        </property>
        <property name="serviceClass">
            <value>be.g00glen00b.rest.RESTClient</value>
        </property>
    </bean>
    <bean id="restClient" class="be.g00glen00b.rest.RESTClient"
        factory-bean="restClientFactory" factory-method="create" />

    <context:component-scan base-package="be.g00glen00b.controller" />
    <context:annotation-config />
</beans>

Wat ik hier eigenlijk doe is dat ik de factory bean van CXF aanroep en hier enkele properties aan meegeef zoals de URL naar de REST API, een interface die de verschillende calls bevat (net zoals bij RESTEasy), dit is gewoon een interface met dezelfde methodes als in de RESTController.

Ten slotte geven we hier ook de provider aan mee zodat CXF de JSON uitvoer terug naar objecten kan omzetten.

Heads up!

De URL die meegegeven wordt zal afhangen van de naam van je service-project wat in mijn geval

cxf-example-service

is.

User class

Vorige keer hebben we aan de class

User

enkele annotaties gegeven om te kunnen werken met XML/JAXB. Deze annotaties zijn nu uiteraard overbodig en mogen verwijderd worden.

Je zal op het internet ook enkele voorbeelden vinden waarbij een JacksonJaxbJsonProvider gebruikt wordt die tevens gebruik maakt van dezelfde annotaties om z’n uitvoer te genereren, maar dat hebben we hier dus niet nodig.

Open dus de class

User

die te vinden is in de package

be.g00glen00b.domain

in het core-project en verwijder de annotaties

@XmlRootElement

,

@XmlElement

en

@XmlAttribute

van alle elementen.

REST Controller

Omdat we in de Spring bean configuratie van het service-project de service-beans moesten meegeven aan de CXF container, moeten we een “naam” geven aan de controller. Vervang dus de annotatie

@Controller

door

@Controller("restController")

in de class

RESTController

te vinden in de package

be.g00glen00b.controller

in het service-project.

Main Controller

Vorige keer hadden we programmatisch een REST client proxy aangemaakt, dit keer hebben we de configuratie hiervan echter in een bean configuratie bestand geplaatst waardoor we nu dus enkel nog de REST client moeten auto-wiren.

Open

MainController

te vinden in de package

be.g00glen00b.controller

in het client-project en vervang het veld client door:

@Autowired
private RESTClient client;

Hiermee is ook de volledige tutorial achter de rug en kunnen we gaan builden.  De project-structuur is ongewijzigd gebleven ten opzichte van vorige keer, dus deze kan je gewoon gaan vergelijken.

Builden

Het laatste wat ons nu nog te doen staat is het project builden. Hiervoor gebruiken we het gekende commando:

mvn clean install

dat we uitvoeren in het parent-project. Indien we dat gedaan hebben kan je in de

target

-map van het client- en service-project het WAR-bestand terug vinden dat we kunnen deployen.

De manier van deployen is dezelfde ten opzichte van in de tutorial rond RESTEasy, we hebben hiervoor een externe webcontainer nodig (bijvoorbeeld Tomcat).

Het resultaat is tevens hetzelfde gebleven:

bill-results

Als we nu echter de service zelf aanroepen via http://localhost:8080/cxf-example-service/api/getUser dan zal je merken dat we geen XML uitvoer meer krijgen maar JSON.

service-result

Apache CXF vs JBoss RESTEasy

Momenteel hebben we dus twee JAX-RS implementaties gezien. De meeste grote Java-related bedrijven hebben ondertussen wel al een implementatie hierop, IBM biedt het aan via WebSphere Application Server en Oracle heeft, via het opgekochtte Sun, Jersey in handen.

Door deze twee voorbeelden naast elkaar te houden zie je ook direct het voordeel van de opgelegde contracten/API’s die Java EE ons aanbiedt. We hebben immers met enkele kleine wijzigingen de code omgezet van één framework naar een ander.

Java EE biedt namelijk een set aan programming interfaces of API’s waardoor het eigenlijk ervoor zorgt dat, zolang de contracten bestaan, het eigenlijk niet uitmaakt welke implementatie je gebruikt. Stel bijvoorbeeld dat vandaag op morgen Apache zou stoppen, dan kost het ons niet veel werk om over te stappen.
Mochten deze regels er niet zijn, dan zouden we alle code moeten aanpassen en implementatie-afhankelijke code moeten gebruiken.

JAXB vs Jackson

We hebben nu ook twee providers gezien om objecten om te zetten naar een beschrijvende taal (JSON en XML). Persoonlijk vind ik de JAX-RS integratie met JAXB minder goed dan deze met Jackson, vooral als het om generics gaat.

Vanaf dat je met JAXB gaat werken met collecties zal je op het internet vaak resultaten terug vinden met wrappers en allerlei omslachtige toestanden. Dit komt omdat in het verleden gekozen is om generics niet mee te compilen in de code waardoor niemand at runtime kennis heeft van welk type een lijst bijvoorbeeld is.

JAXB vangt dit op door te werken met wrappers en extra annotaties bestaande uit de class names die je gaat gebruiken in je generics. Jackson daarentegen heeft echt een expliciete JAX-RS provider die dankzij die provider te weten komt via de interface wat de return-type is.

Zelf vind ik Jackson logischer werken (geen nood aan annotaties of wrappers), maar da’s een persoonlijke keuze.

Download project

Download – cxf-example.rar (9,2 kB)

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.