10 March 2016

Introduction

Spring is a configuration (annotation/xml) based framework. One annotation in spring does lots of job for us. Needless to say spring configurations are very powerful in nature. However, as a part of the continuous improvement in Spring framwork, Spring boot has been introduced to serve the following advantages

  • Creates stand alone spring applications. Embedded Tomcat/Jetty comes with spring boot so developer does not require to deploy the application in any web application.
  • Automatically configure Spring whenever possible
  • Provide production-ready features such as metrics, health checks and externalized configuration
  • There is no need of xml configuration
  • No need to add all dependancies in POM. It adds all required latest version of the dependencies.

In this blog I am going to discuss the followings

  • Write and execute a Stanalone Spring boot application
  • Deploy Spring boot application in tomcat
  • Integration of Junit with Spring boot application
  • Exception handling for Spring Restful webservices
  • Configuration of the MockMVC to call the webservices directly.

Steps to write code

The spring boot project structure is given below

Required files

Below are the minimum required files to achieve the goals defined above

SL NO Class Name Description
1 pom.xml Build files having required dependencies added
2 com.ashish.config.WebConfig This configuration file is useful to run the spring boot application stand-alone or in real tomcat server.
3 com.ashish.config.SpringBootAppWS This is a dummy rest controller publishes restful web service.
4 com.ashish.config.GlobalDefaultExceptionHandler This class handles any exception occurs during the service invocation.
5 com.ashish.test.junit.WebserviceTestSuite Junit test suite to run WebserviceTest1 and WebserviceTest2 junit classes
6 com.ashish.test.junit.WebserviceTest1 Junit test class tests PNR service (exposed in SpringBootAppWS class) and exception services which throws default exception and custom exceptions. MockMVC is used to call the webservices directly.
7 com.ashish.test.junit.WebserviceTest2 Junit test class tests Advantage service ( (exposed in SpringBootAppWS class) and a dummy service. MockMVC is used to call the webservices directly.
pom.xml:

This file is having required dependencies added


<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>com.ashish.spring.boot</groupId>
	<artifactId>SpringBootAppWS</artifactId>
	<version>1.0-BETA</version>
	<packaging>war</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.3.3.RELEASE</version>
	</parent>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.3</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.6</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>
		
		<!-- Below dependency has provided scope defined because during deployment in tomcat this jar is not required.
			This jar is required to execute the stand alone application with embedded tomcat-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>
</project>
com.ashish.config.WebConfig:

This configuration file is useful to run the spring boot application stand-alone or in real tomcat server. Note that @ComponentScan has not been used. Instead it provides aliases to customize the attributes of @EnableAutoConfiguration and @ComponentScan


package com.ashish.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication(scanBasePackages="com.ashish")
public class WebConfig extends SpringBootServletInitializer {

	/**
	 * This method configures the web application
	 */
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WebConfig.class);
    }

    /**
     * Run the spring boot application in embedded tomcat 
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        SpringApplication.run(WebConfig.class, args);
    }

}
com.ashish.config.SpringBootAppWS:

This is a dummy rest controller publishes few restful web services.


package com.ashish.app;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.ashish.exception.CustomException;

@RestController
@RequestMapping("/rest")
public class SpringBootAppWS {
	@RequestMapping(path="/getPNR", method = RequestMethod.GET, headers="Accept=application/json",produces="application/json")
    public String getPNR() {
        return "PNR";
    }

	@RequestMapping(path="/getAdv", method = RequestMethod.GET, headers="Accept=application/json",produces="application/json")
    public String getAdv() {
        return "Adv#";
    }
	
	@RequestMapping(path="/getDefaultException", method = RequestMethod.GET, headers="Accept=application/json",produces="application/json")
    public String getDefaultException() {
        String name = null;
        System.out.println(name.toLowerCase());   // Null pointer exception will be thrown from here
		return null;
    }
	
	@RequestMapping(path="/getCustomException", method = RequestMethod.GET, headers="Accept=application/json",produces="application/json")
    public String getCustomException() throws CustomException {
		System.out.println("Testing Custom Exception"); 
		if(true) {
        	throw new CustomException("Custom Exception");
        }
		return null;
    }
}

com.ashish.config.GlobalDefaultExceptionHandler:

Checked and un-checked exception handing is shown in this class. For any Custom uncaught exception in the program code will get caught in this class and it retuns meaningful error message with HTTP status code accordingly.


package com.ashish.config;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.ashish.exception.CustomException;

@ControllerAdvice
public class GlobalDefaultExceptionHandler {
    @ExceptionHandler({CustomException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    public Map<String, String> unauthorizedAccess(Exception e) {
        Map<String, String> exception = new HashMap<String, String>();

        System.out.println("unauthorized Access to the API: " + e.getMessage());
        exception.put("code", "401");
        exception.put("reason", e.getMessage());

        return exception;
    }
    
    @ExceptionHandler({Exception.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.EXPECTATION_FAILED)
    public Map<String, String> exceptionInProcessing(Exception e) {
        Map<String, String> exception = new HashMap<String, String>();

        System.out.println("Unable to process. Some error occured: " + e.getMessage());
        exception.put("code", "417");
        exception.put("reason", e.toString());

        return exception;
    }
}
com.ashish.test.junit.WebserviceTestSuite:

Junit test suite to run WebserviceTest1 and WebserviceTest2 junit classes.


package com.ashish.test.junit;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
	WebserviceTest1.class,
	WebserviceTest2.class
})
public class WebserviceTestSuite {
	
}

com.ashish.test.junit.WebserviceTest1:

Junit test class tests PNR service (exposed in SpringBootAppWS class) and exception services which throws default exception and custom exceptions.


package com.ashish.test.junit;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.ashish.config.WebConfig;
import com.fasterxml.jackson.databind.ObjectMapper;


@RunWith(SpringJUnit4ClassRunner.class)
@ComponentScan(basePackages = "com.ashish.test")
@ContextHierarchy({
	  @ContextConfiguration(classes={WebConfig.class})
})
@WebAppConfiguration
public class WebserviceTest1 {
	
	private MockMvc mockMvc;
	@Autowired
	private WebApplicationContext wac;
	
	private ObjectMapper om;
	
	@Before
	public void runBefore() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
		om = new ObjectMapper();
	}

	@Test
	public void getPNR() {
		try {
			MvcResult result = this.mockMvc.perform(get("/rest/getPNR")).andExpect(status().isOk())
					.andExpect(content().contentType("application/json"))
					.andReturn();
			System.out.println(result.getResponse().getContentAsString());
			assertEquals("PNR", result.getResponse().getContentAsString());
		} catch(Exception e) {
			System.out.println("Error while retrieving PNR");
			e.printStackTrace();
		}
	}
	
	@Test
	public void getDummy() {
		System.out.println("Ashish");
	}
	
	@Test
	public void testDefaultException() {
		try {
			MvcResult result = this.mockMvc.perform(get("/rest/getDefaultException")).andExpect(status().is4xxClientError())
					.andExpect(content().contentType("application/json"))
					.andReturn();
			System.out.println(result.getResponse().getContentAsString());
			Map<String, String> error = om.readValue(result.getResponse().getContentAsString(), Map.class);
			assertEquals("{\"reason\":\"java.lang.NullPointerException\",\"code\":\"417\"}", result.getResponse().getContentAsString());
			assertEquals("417", error.get("code"));
			assertEquals("java.lang.NullPointerException", error.get("reason"));
		} catch(Exception e) {
			System.out.println("Error while Testing default exception");
			e.printStackTrace();
		}
	}
	
	@Test
	public void testCustomException() {
		try {
			MvcResult result = this.mockMvc.perform(get("/rest/getCustomException")).andExpect(status().is4xxClientError())
					.andExpect(content().contentType("application/json"))
					.andReturn();
			System.out.println(result.getResponse().getContentAsString());
			Map<String, String> error = om.readValue(result.getResponse().getContentAsString(), Map.class);
			assertEquals("{\"reason\":\"Custom Exception\",\"code\":\"401\"}", result.getResponse().getContentAsString());
			assertEquals("401", error.get("code"));
			assertEquals("Custom Exception", error.get("reason"));
			
		} catch(Exception e) {
			System.out.println("Error while Testing custom exception");
			e.printStackTrace();
		}
	}
	
}
com.ashish.test.junit.WebserviceTest2:

Junit test class tests Advantage service ( (exposed in SpringBootAppWS class) and a dummy service.


package com.ashish.test.junit;

import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.ashish.config.WebConfig;


@RunWith(SpringJUnit4ClassRunner.class)
@ComponentScan(basePackages = "com.ashish.test")
@ContextHierarchy({
	  @ContextConfiguration(classes={WebConfig.class})
})
@WebAppConfiguration
public class WebserviceTest2 {
	
	private MockMvc mockMvc;
	@Autowired
	private WebApplicationContext wac;
	
	@Before
	public void runBefore() {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
	}
	

	@Test
	public void getAdv() {
		try {
			MvcResult result = this.mockMvc.perform(get("/rest/getAdv")).andExpect(status().isOk())
					.andExpect(content().contentType("application/json"))
					.andReturn();
			System.out.println(result.getResponse().getContentAsString());
			assertEquals("Adv#", result.getResponse().getContentAsString());
		} catch(Exception e) {
			System.out.println("Error while retrieving PNR");
			e.printStackTrace();
		}
	}
	
	@Test
	public void getDummy() {
		System.out.println("Ashish");
	}
	
}

Run the application

This application can be run in one of the following ways

  • Stanalone mode : This application can be run as normal java class as it has a public static void main(). By default this application will run in embedded tomcat environment.
  • WAR deployment mode : The created war can be deployed and run in tomcat server
  • Junit mode: The application can be run and unit tested using Junit.
    • Execute com.ashish.test.junit.WebserviceTestSuite Junit class to test the application


blog comments powered by Disqus
J2EE,SOAP,RESTful,SVN,PMD,SONAR,JaCoCo,HTTP,API,MAVEN,AngularJS,GitHub,LDAP,AOP,ORM,JMS,MVC,AWS,SQL,PHP,H2DB,JDBC