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.
- Run com.ashish.config.WebConfig as normal java class and hit the following URL to test: http://localhost:8080/rest/getPNR
- WAR deployment mode : The created war can be deployed and run in tomcat server
- Deploy the WAR in any tomcat server and hit the following URL : http://localhost:8080/SpringBootAppWS/rest/getPNR. Note that the context root is present in the URL
- Junit mode: The application can be run and unit tested using Junit.
- Execute com.ashish.test.junit.WebserviceTestSuite Junit class to test the application