Wednesday, May 16, 2012

Webservices with Spring 3 + Apache CXF

This example will consist of a web app that publishes a simple web service using Apache CXF + Spring. It will include a web service client test too, and a lightweight server (Jetty) to quick test with. Before running the JUnit test through Eclipse IDE or Maven, you have to start your server manually. This configuration is going to be explained.

Versions used:
- Maven 3
- Apache CXF 2.6
- Spring 3.1.1-RELEASE


This is the structure of our archetyped webapp:




pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>ar.com.pabloExample</groupId>
 <artifactId>spring-cxf-example</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>spring-cxf-example Maven Webapp</name>
 <url>http://maven.apache.org</url>

 <repositories>
  <repository>
   <id>springsource-repo</id>
   <name>SpringSource Repository</name>
   <url>http://repo.springsource.org/release</url>
  </repository>
 </repositories>

 <dependencies>

  <!-- Spring dependencies -->

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>compile</scope>
  </dependency>

  <!-- Apache CXF for webservices -->

  <dependency>
   <groupId>org.apache.cxf</groupId>
   <artifactId>cxf-api</artifactId>
   <version>2.6.0</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.apache.cxf</groupId>
   <artifactId>cxf-rt-frontend-jaxws</artifactId>
   <version>2.6.0</version>
   <scope>compile</scope>
  </dependency>

  <dependency>
   <groupId>org.apache.cxf</groupId>
   <artifactId>cxf-rt-transports-http</artifactId>
   <version>2.6.0</version>
   <scope>compile</scope>
  </dependency>

  <!-- For testing purposes -->

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>3.1.1.RELEASE</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
  </dependency>

 </dependencies>

 <build>
  <finalName>spring-cxf-example</finalName>

  <plugins>

   <!-- With this you can start the server by doing mvn jetty:run -->
   <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>maven-jetty-plugin</artifactId>
    <version>6.1.26</version>
    <configuration>
     <scanIntervalSeconds>3</scanIntervalSeconds>
    </configuration>
   </plugin>

   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>

  </plugins>

 </build>

</project>


HelloWorldService

@WebService
public interface HelloWorldService {
 
 public void sayHello();
}

In order to work with webservices, we must use interfaces for our services...


HelloWorldServiceBean

@WebService(endpointInterface = "ar.com.pabloExample.services.HelloWorldService")
public class HelloWorldServiceBean implements HelloWorldService {

 @Override
 public void sayHello() {
  System.out.println("Hello World!!!");
 }

}

The endpointInterface must point to the interface we are implementing (that's why interfaces are a must).


service-definition-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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.1.xsd">

 <bean id="helloWorldService" class="ar.com.pabloExample.services.HelloWorldServiceBean" />

</beans>

Here we define a normal service bean.


webservice-definition-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<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-3.1.xsd
  http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

 <import resource="classpath:service-definition-beans.xml"/>

 <jaxws:endpoint id="helloWorld" implementor="#helloWorldService" address="/HelloWorld" />

</beans>

The bean defined in service-definition-beans.xml can be moved here instead of importing that resource. You may want to have separate bean xml files, it is important to separate by functionality (e.g. DAO beans, webservice beans, test beans...). By doing implementor="#helloWorldService" we are making reference to a Spring bean with id="helloWorldService"


service-definition-beans-test.xml

<?xml version="1.0" encoding="UTF-8"?>
<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-3.1.xsd
  http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

 <jaxws:client id="helloWorldClient" serviceClass="ar.com.pabloExample.services.HelloWorldService"
  address="http://localhost:8080/spring-cxf-example/HelloWorld" />

</beans>

Here we create the CXF webservice client which we will invoke during our test by injecting a Spring bean. For this client to be successful, the server has to be up and running with the application.


web.xml

<!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>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>WEB-INF/webservice-definition-beans.xml</param-value>
 </context-param>

 <servlet>
  <servlet-name>CXFServlet</servlet-name>
  <display-name>CXF Servlet</display-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>

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

</web-app>

This is were we configure CXFServlet to scan all our request and Spring ContextLoaderListener to scan all of our bean configuration files deployed.


Let's test the webservice!


HelloWorldServiceTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/service-definition-beans-test.xml" })
public class HelloWorldServiceTest {

 @Autowired
 @Qualifier("helloWorldClient")
 private HelloWorldService helloWorldClient;

 @Test
 public void helloWorldClientTest() {
  
  helloWorldClient.sayHello();
 }

}

Now, here we invoke the previous mentioned webservice client. Just do mvn jetty:run from a console to start the server and run the test via Eclipse IDE or Maven!!


Get the complete code!


You can checkout the code from here:

https://subversion.assembla.com/svn/pablo-examples/spring-cxf-example/

To run the tests successfully just do mvn clean package -Dmaven.test.skip=truemvn jetty:run and then mvn test.


Finally...


The next step is to run this test as an integration test because this test needs preconditions, like the server up, so it cannot be part of normal tests. I will explain the implementation differences between a test and integration-test with an example in another post.

Here is another useful related post from the official Apache CXF home.

15 comments:

  1. Excelente post!! A veces parece que estamos sincronizados con los temas, jaja. Yo venía medio atrasado con la lectura de mi reader y recién vi este post ayer. De todas formas acabo de editar la entrada de mi blog (http://elblogdelfrasco.blogspot.com.ar/2012/05/armando-una-plataforma-soa-con-apache.html) para agregar en la parte que hablo de Apache CXF un link a este tutorial. Muy bueno!!

    ReplyDelete
  2. Gracias Adri! jaja es verdad, parece que estuvieramos sincronizados aunque nuestros blogs estén encarados distinto. Sí, ví el post sobre la Apache SOA Platform (por cierto, muy acertado nombre), y como siempre muy clara la introduccion que haces de los temas!

    ReplyDelete
  3. Hi There,

    I am a slightly tight spot where by I have currently working cxf webservice running but with 2.5.2 and spring 3.0.

    So far I am battling to even get 1 webservice running with cxf2.6.0 & spring3.1.1. I have now come across your blog. Could you please assist me with where I can pick up your working example source. I really appreciate it.

    Many Thanks
    Sanjay

    ReplyDelete
  4. Hi Sanjay, sure, I've just updated the post with the URL of the source code. Hope it helps.

    ReplyDelete
  5. Very nice tutorial. Even a newbie can get this code and successfully run it and see how it works. This is really a starting point of a newbie in Java web service.

    ReplyDelete
  6. Hi Pablo Oscar Felitti,
    Thank you so much for providing such a great and simple tutorial.

    Regards,
    Pal.

    ReplyDelete
  7. I am unable to get it to run in tomcat

    ReplyDelete
  8. I checked out your project from svn.
    While using the command mvn jetty:run, I am getting the following error. Kindly advise.

    F:\CXF WORKSPACE\cxf-spring-test>mvn jetty:run
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'jetty'.
    Downloading: http://zafindev.com/artifactory/zafin-repos/org/mortbay/jetty/jetty-maven-plugin/7.4.2.v20110526/jetty-maven-plugin-7.4.2.v20110526.pom
    Downloading: http://zafindev.com/artifactory/zafin-repos/org/mortbay/jetty/jetty-maven-plugin/7.4.2.v20110526/jetty-maven-plugin-7.4.2.v20110526.pom
    Downloading: http://zafindev.com/artifactory/zafin-repos/org/mortbay/jetty/jetty-maven-plugin/7.4.2.v20110526/jetty-maven-plugin-7.4.2.v20110526.pom
    Downloading: http://zafindev.com/artifactory/zafin-repos/org/mortbay/jetty/jetty-maven-plugin/7.4.2.v20110526/jetty-maven-plugin-7.4.2.v20110526.pom
    Downloading: http://zafindev.com/artifactory/zafin-repos/org/mortbay/jetty/jetty-maven-plugin/7.4.2.v20110526/jetty-maven-plugin-7.4.2.v20110526.pom
    [INFO] ------------------------------------------------------------------------
    [ERROR] BUILD ERROR
    [INFO] ------------------------------------------------------------------------
    [INFO] Error building POM (may not be this project's POM).


    Project ID: org.mortbay.jetty:jetty-maven-plugin

    Reason: POM 'org.mortbay.jetty:jetty-maven-plugin' not found in repository: Unable to download the artifact from any repository

    org.mortbay.jetty:jetty-maven-plugin:pom:7.4.2.v20110526

    from the specified remote repositories:
    zafin-repos (http://zafindev.com/artifactory/zafin-repos)
    for project org.mortbay.jetty:jetty-maven-plugin


    [INFO] ------------------------------------------------------------------------
    [INFO] For more information, run Maven with the -e switch
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 5 seconds
    [INFO] Finished at: Mon Oct 22 12:45:42 IST 2012
    [INFO] Final Memory: 2M/15M
    [INFO] ------------------------------------------------------------------------

    Regards
    Jaseem, A.

    ReplyDelete
  9. Hi, you're trying to download the jetty plugin from a repo (zafin-repos) that does not have that plugin. Either tell the manager of that repo to include it or disable that custom repo.

    ReplyDelete
  10. in your article you say "This is the structure of our archetyped webapp...".
    which archetype?

    tks.

    ReplyDelete
  11. The webapp archetype I was making reference to is the standard one for webapps.

    It is called "maven-archetype-webapp (A simple Java web application)"

    ReplyDelete
  12. i m getting exception when i start the jetty server in console

    rser$SpringEndpointImpl] from ClassLoader [ContextLoader@Archetype Created Web A
    pplication] failed; nested exception is java.lang.NoClassDefFoundError: javax/xm
    l/ws/EndpointReference:
    java.lang.NoClassDefFoundError: javax/xml/ws/EndpointReference
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
    at java.lang.Class.getDeclaredConstructors(Class.java:1836)
    at org.springframework.beans.factory.support.ConstructorResolver.autowir
    eConstructor(ConstructorResolver.java:157)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBean
    Factory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1035)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBean
    Factory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:939)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBean
    Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBean
    Factory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getOb
    ject(AbstractBeanFactory.java:294)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistr
    y.getSingleton(DefaultSingletonBeanRegistry.java:225)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBe
    an(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean
    (AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.
    preInstantiateSingletons(DefaultListableBeanFactory.java:585)
    at org.springframework.context.support.AbstractApplicationContext.finish
    BeanFactoryInitialization(AbstractApplicationContext.java:913)

    ReplyDelete
  13. Hi, maybe it's related to the JVM version you're using. Try upgrading it to a more recent version.

    ReplyDelete
  14. Thanks a lot Pablo Feliti.I am new to webservices,but through your post i got basic idea how web services should work.

    Thanks
    srinivas

    ReplyDelete