Thursday, 6 July 2017

Spring MVC Java Config : Part 3 RESTful Web Services


REST stands for Representational State Transfer.

It’s an is an architectural style which can be used to design web services, that can be consumed from a variety of clients.

The core idea is that, rather than using complex mechanisms such as CORBA, RPC or SOAP to connect between machines, simple HTTP/HTTPS is used to make calls among them.


1. TOOLS AND ENV

IDE : Spring Tool Suite 3.7.3
JDK : 1.8
Tomcat : 8.0.18
Spring : 4.2.6.RELEASE




2. POM.XML

<?xml version="1.0" encoding="UTF-8"?>
<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>org.junjun.spring.tutorials</groupId>
<artifactId>spring-mvc-restful-api</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<org.springframework.version>4.2.6.RELEASE</org.springframework.version>
<slf4j.version>1.7.10</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.2.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- http://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
<!-- <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId>
<version>2.7.4</version> </dependency> -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub

1. add spring-oxm for jaxb
2. add jackson-databind for json




3. RestController

package org.junjun.spring.tutorials.web.controller;
import java.util.List;
import javax.validation.Valid;
import org.apache.log4j.Logger;
import org.junjun.spring.tutorials.bean.EntityList;
import org.junjun.spring.tutorials.bean.ToDoEntity;
import org.junjun.spring.tutorials.service.ToDoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/todo")
public class ToDoRestController {
private static final Logger logger = Logger.getLogger(ToDoRestController.class);
@Autowired
private ToDoService toDoService;
@RequestMapping(value = "", method = RequestMethod.GET)
public ResponseEntity<?> list(Model model) {
ResponseEntity<?> response = new ResponseEntity<>(HttpStatus.NO_CONTENT);
List<ToDoEntity> toDoList = toDoService.getAll();
EntityList<ToDoEntity> wapper = new EntityList<>(toDoList);
if (toDoList.size() > 0) {
response = new ResponseEntity<>(wapper, HttpStatus.OK);
}
return response;
}
@RequestMapping(value = "create", method = RequestMethod.POST)
public ResponseEntity<ToDoEntity> create(@RequestBody @Valid ToDoEntity todo, BindingResult result) {
ToDoEntity created = null;
ResponseEntity<ToDoEntity> response = null;
if (result.hasErrors()) {
for (ObjectError err : result.getAllErrors()) {
logger.warn(err.getDefaultMessage());
}
response = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} else {
created = toDoService.create(todo);
response = new ResponseEntity<>(created, HttpStatus.OK);
}
return response;
}
}


1. @RestController annotation marks all the methods with @RequestMapping becoming @ResponseBody
2.  JAXB won't be able to handle List<ToDoEntity> ,  we must create a wapper in case of converting from list of java beans to XML
3. Below are the patterns for restful web services , we would code GET and POST here , others are similar
  • GET request to /api/todo/ returns a list of todos    
  • GET request to /api/todo/1 returns the todo with ID 1
  • POST request to /api/todo/ with a todo object as JSON creates a new todo    
  • PUT request to /api/todo/3 with a todo object as JSON updates the todo with ID 3
  • DELETE request to /api/todo/4 deletes the todo with ID 4
  • DELETE request to /api/todo/ deletes all the users




4. POJO

package org.junjun.spring.tutorials.bean;
import static org.junjun.spring.tutorials.common.Const.DF_DD_MMM_YYYY;
import java.io.Serializable;
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.hibernate.validator.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
@XmlRootElement(name = "todo")
@XmlAccessorType(XmlAccessType.NONE)
public class ToDoEntity implements Serializable {
@NotEmpty
@XmlElement(name = "content")
String content;
Date createdDate;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@JsonFormat(pattern = "dd MMM yyyy")
@JsonProperty("date")
public Date getCreatedDate() {
return createdDate;
}
@JsonIgnore
public String getCreatedDateDisplay() {
return DF_DD_MMM_YYYY.format(createdDate);
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
}
view raw ToDoEntity.java hosted with ❤ by GitHub

package org.junjun.spring.tutorials.bean;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Created for JAXB.
*
* @author Justin
*
*/
@XmlRootElement(name = "data")
public class EntityList<T> {
private List<T> items;
public EntityList() {
items = new ArrayList<T>();
}
public EntityList(List<T> items) {
this.items = items;
}
@XmlAnyElement(lax=true)
public List<T> getItems() {
if (items == null) {
items = new ArrayList<>();
}
return items;
}
}
view raw EntityList.java hosted with ❤ by GitHub


1.@XmlAccessorType(XmlAccessType.NONE) , JAXB will create bindings for annotated fields and methods
2. @XmlAnyElement(lax=true) annotation, what this means is that when dealing with this property the @XmlRootElement of the referenced object will be used.  If lax is set to false then the content will be unmarshalled as DOM nodes.





5. ADD MESSAGE CONVERTER TO SPRING MVC

package org.junjun.spring.tutorials.config;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan({ "org.junjun.spring.tutorials.web" })
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Register Bean Validator.
*
* @return
*/
@Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
/**
* Configure MessageSource to provide internationalized messages
*
*/
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder mapper = new Jackson2ObjectMapperBuilder();
MappingJackson2HttpMessageConverter json = new MappingJackson2HttpMessageConverter(mapper.build());
converters.add(json);
converters.add(marshallingMessageConverter());
}
@Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
converter.setMarshaller(jaxbMarshaller());
converter.setUnmarshaller(jaxbMarshaller());
return converter;
}
@Bean
public Jaxb2Marshaller jaxbMarshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.cn.junjun.spring.sample.bean");
Map<String, Object> props = new HashMap<>();
props.put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setMarshallerProperties(props);
return marshaller;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**").addResourceLocations("/js/", "classpath:/js/");
}
/**
* Configure View Resolver
*/
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
view raw WebConfig.java hosted with ❤ by GitHub

1. you may see some of spring mvc tutorial use Jaxb2RootElementHttpMessageConverter for JAXB, refer below on why it is not perferred
https://jira.spring.io/browse/SPR-13530
https://jira.spring.io/browse/SPR-10262
2. JAXB would fail at EntityList<T> without below line which add all classes under the package to JAXB context
marshaller.setPackagesToScan("com.cn.junjun.spring.sample.bean");





6. JSP FOR RESTful WS

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="spring">
<meta name="keywords" content="spring">
<title></title>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="padding-top: 10%">
<div class="row">
<form:form id="form-todo" class="form-horizontal" modelAttribute="newToDo" action="">
<div class="form-group">
<div class="col-md-offset-2 col-md-6">
<form:input id="todoContent" class="form-control" path="content"
placeholder="" />
</div>
<div class="col-md-2">
<button id="rest-create-btn" class="btn btn-success btn-block">Create</button>
</div>
<label for="todoContent" class="col-md-4 control-label"
style="text-align: left;"><spring:bind path="content">
<c:if test="${status.error}">
<form:errors path="content" class="text-danger" />
</c:if>
</spring:bind></label>
</div>
</form:form>
</div>
<div class="row">
<div class="col-md-offset-2 col-md-10">
<ul id="ul-todo-list">
<c:forEach items="${toDoList}" var="toDo">
<li>${toDo.createdDateDisplay}&nbsp;:&nbsp;${toDo.content}</li>
</c:forEach>
</ul>
</div>
</div>
<input type="hidden" id="rest-url-create" value="${pageContext.request.contextPath}/api/todo/create" />
</div>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="${pageContext.request.contextPath}/js/todo/rest/list.js"></script>
</body>
</html>
view raw list.jsp hosted with ❤ by GitHub






7. JS for JSP

(function() {
$(document).ready(function() {
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || "");
} else {
o[this.name] = this.value || "";
}
});
return o;
};
$(document).on("#form-todo").submit(function(event) {
event.preventDefault();
return false;
});
$(document).on("click", "#rest-create-btn", function() {
var form = $("#form-todo");
var url = $("#rest-url-create").val();
var data = JSON.stringify(form.serialize());
data = JSON.stringify(form.serializeObject());
$.ajax({
type : "POST",
url : url,
data : data,
contentType: "application/json; charset=utf-8",
dataType: "json",
success : function(response) {
var output="<li>" + response.date + " : " +response.content + "</li>";
$("#ul-todo-list").append(output);
$("#todoContent").val("");
}
});
});
});
})();
view raw list.js hosted with ❤ by GitHub







8. TEST with POSTMAN




POSTMAN test scripts are available here : https://www.getpostman.com/collections/cc404ecd3f8648e83827

 




9. SOURCE CODE

https://github.com/junjun-dachi/spring-4-samples/tree/master/spring-4-mvc-sample-03





Reference :

1. https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc 
2. https://spring.io/guides/gs/rest-service/
3. http://spring.io/guides/tutorials/bookmarks/





No comments:

Post a Comment

Flag Counter