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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
1. add spring-oxm for jaxb
2. add jackson-databind for json
3. RestController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ 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} : ${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> |
7. JS for JSP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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(""); | |
} | |
}); | |
}); | |
}); | |
})(); |
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