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.

Saturday, May 12, 2012

Testing with JUnit + Spring

In this post I want to share the most convenient way to test Spring beans using JUnit. The idea is to test using dependency injection techniques instead of performing a manual injection.

The versions I used to create this project are:

- Spring 3.1.1-RELEASE
- JUnit 4
- Maven 3


Configuration files:


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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>ar.com.pabloExample</groupId>
 <artifactId>spring-junit-example</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>spring-junit-example</name>
 <url>http://maven.apache.org</url>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <dependencies>

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

  <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>
</project>


test-beans.xml

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

 <bean class="ar.com.pabloExample.services.MessageService" id="messageService"/>
 
</beans>

We just define a normal Spring bean in the test classpath.


Creating the test


BeanInjectionNewWayTest

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-beans.xml" })
public class BeanInjectionNewWayTest {
 
 @Autowired
 @Qualifier("messageService")
 private MessageService messageService;

 
 @Test
 public void test() {
  messageService.printMessage();
 }

}

The @RunWith(SpringJUnit4ClassRunner.class) tells JUnit to run JUnit using the SpringJUnit4ClassRunner class as the internal core. This allows the rest of the annotations (except for @Test) to work as expected. @ContextConfiguration brings up all the XML bean files in a context and we can dispone of those beans anytime in any test.

@Autowired injects a bean, if no @Qualifier specified, it is done BY TYPE; if @Qualifier is specified, it is injected BY BEAN ID. Let's see this in more detail:

In this example, we just have one bean of ar.com.pabloExample.services.MessageService type. So we can just put the @Autowired (no @Qualifier) and everything works as expected because it matches BY TYPE.

If we had the following beans file instead:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

 <bean class="ar.com.pabloExample.services.MessageService" id="messageService"/>
 <bean class="ar.com.pabloExample.services.MessageService" id="messageService2"/>
 
</beans>

Then we cannot match only BY TYPE because we have now two beans of the same type. So, appart from @Autowired we must use @Qualifier with the bean id of the explicit bean to inject.


Testing JUnit + Spring the old way!


Just to keep in mind, here are two other ways of to use Spring beans while testing with old versions of JUnit and Spring. These ways are still being used in many enterprise applications:


BeanInjectionOldWayTest

public class BeanInjectionOldWayTest extends AbstractSingleSpringContextTests {
 
 private MessageService messageService;

 @Override
 protected String[] getConfigLocations() {
  String[] locations = { "/test-beans.xml" };
  return locations;
 }

 @Override
 public void onSetUp() {
  messageService = (MessageService) this.applicationContext.getBean("messageService");
 }

 public void testInject() throws Exception {
  
  messageService.printMessage();
 }

}


BeanInjectionOldWay2Test

public class BeanInjectionOldWay2Test {
 
 private ApplicationContext applicationContext;
 private MessageService messageService;

 @Before
 public void init() {
  applicationContext = new ClassPathXmlApplicationContext("test-beans.xml");
  messageService = (MessageService) applicationContext.getBean("messageService");
 }
 
 @Test
 public void test() {
  messageService.printMessage();
 }

}