RESTful服务是“前后端分离”架构中的主要功能:后端应用对外暴露RESTful服务;前端应用则通过RESTful服务与后端应用交互。RESTful服务的数据格式既可是JSON的,也可是XML的。
开发基于 JSON 的 RESTful 服务非常简单, 只要使用@RestController注解修饰控制器类,或者使用@ResponseBody修饰处理方法即可。(@RestController和@Controller的区别就在于,@RestController会自 动为每个处理方法都添加@ResponseBody注解)
Spring Boot内置了如下三种JSON库的支持:Jackson、Gson和JSON-B。正如从前面所看到的,如果没有任何特别的配置,Spring Boot默认 选择Jackson作为JSON库。实际上,Jackson的自动配置由spring-boot-sta rter-json.jar提供,只要Spring Boot检测到系统类加载路径中有Jackson依 赖库,Spring Boot就会自动创建基于Jackson的ObjectMapper。
1,整合JSON
1.1,默认方式
JSON是目前主流的前后端数据传输方式,SpringMVC中使用消息转换器HttpMessageConverter对JSON转换提供了很好的支持,在SpringBoot中更进一步,对相关配置做了更进一步的简化。如果没有任何特别的配置,Spring Boot默认选择Jackson作为JSON库。实际上,Jackson的自动配置由spring-boot-starter-json.jar提供,只要Spring Boot检测到系统类加载路径中有Jackson依 赖库,Spring Boot就会自动创建基于Jackson的ObjectMapper。
package com.springboot.ysy.Pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Date;
public class Book {
private String name;
private String author;
@JsonIgnore //转换为JSON的时候忽略此项
private Float price;
@JsonFormat(pattern = "yyyy-MM-dd") //设置转换JSON后的格式
private Date publicationDate;
//省略get和set方法
}package com.springboot.ysy.Controller;
import com.springboot.ysy.Pojo.Book;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
class BookController {
@RequestMapping("/book")
public Book book(){
Book book = new Book();
book.setAuthor("罗贯中");
book.setName("三国演义");
book.setPrice(30f);
book.setPublicationDate(new Date());
return book;
}
}
1.2,Gson
Gson是Google开源JSON解析框架,使用Gson,需要先移除默认的jackson-databin,然后加入Gson依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson </artifactId>
</dependency>
</dependencies>package com.springboot.ysy.Pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Date;
public class Book {
private String name;
private String author;
protected Float price;
private Date publicationDate;
//省略get和set
}
需要提供Gson的自动转换类GsonHttpMessageConvertersConfiguration。
package com.springboot.ysy.Controller;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import java.lang.reflect.Modifier;
@Configuration
public class GsonConfig {
@Bean
GsonHttpMessageConverter gsonHttpMessageConverter() {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("yyyy-MM-dd"); //自定义日期类型
builder.excludeFieldsWithModifiers(Modifier.PROTECTED); //设置Gson解析时Protected字段被过滤掉
Gson gson = builder.create();
converter.setGson(gson);
return converter;
}
}
1.3,fastjson
fastjson是阿里巴巴的一个开源JSON解析框架,是目前JSON解析速度最快的开源框架,该框架也可以集成到Spring Boot中。不同于Gson,fastjson继承完成后不能立马使用,需要开发者提供响应的HttpMessageConverter后才可使用。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
另外,还需要配置响应编码,否则返回的JSON中文会乱码,在application.properties中添加:
server.servlet.encoding.force-response=truepackage com.springboot.ysy.Controller;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.nio.charset.Charset;
@Configuration
public class MyFastJsonConfig {
@Bean
FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setDateFormat("yyyy-MM-dd");
config.setCharset(Charset.forName("UTF-8"));
config.setSerializerFeatures(
SerializerFeature.WriteClassName,
SerializerFeature.WriteMapNullValue,
SerializerFeature.PrettyFormat,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteNullStringAsEmpty
);
converter.setFastJsonConfig(config);
return converter;
}
}
2,整合视图(Thymeleaf)
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置控制器
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.List;
@Controller
public class BookController {
@GetMapping("/books")
public ModelAndView books() {
List<Book> books = new ArrayList<>();
Book b1 = new Book();
b1.setId(1);
b1.setAuthor("罗贯中");
b1.setName("三国演义");
Book b2 = new Book();
b2.setId(2);
b2.setAuthor("曹雪芹");
b2.setName("红楼梦");
books.add(b1);
books.add(b2);
ModelAndView mv = new ModelAndView();
mv.addObject("books", books);
mv.setViewName("books");
return mv;
}
}
创建返回视图
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>图书列表</title>
</head>
<body>
<table border="1">
<tr>
<td>图书编号</td>
<td>图书名称</td>
<td>图书作者</td>
</tr>
<tr th:each="book:${books}">
<td th:text="${book.id}"></td>
<td th:text="${book.name}"></td>
<td th:text="${book.author}"></td>
</tr>
</table>
</body>
</html>
3,CORS跨域问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jquery3.3.1.js"></script>
</head>
<body>
<div id="contentDiv"></div>
<div id="deleteResult"></div>
<input type="button" value="提交数据" onclick="postData()"><br>
<input type="button" value="删除数据" onclick="deleteData()"><br>
<script>
function deleteData() {
$.ajax({
url:'http://localhost:8080/book/99',
type:'delete',
success:function (msg) {
$("#deleteResult").html(msg);
}
})
}
function postData() {
$.ajax({
url:'http://localhost:8080/book/add',
type:'post',
data:{name:'三国演义'},
success:function (msg) {
$("#contentDiv").html(msg);
}
})
}
</script>
</body>
</html>package com.example.demo;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/book")
public class BookController {
@PostMapping("/add")
@CrossOrigin(value = "*") //一劳永逸,哈哈哈
public String addBook(String name){
return "receive:"+name;
}
@DeleteMapping("/{id}")
@CrossOrigin(value = "http://localhost:8080",maxAge = 1800,allowedHeaders = "*")
public String deleteBook(@PathVariable Long id){
return String.valueOf(id);
}
}
@CrossOrigin:value表示允许来自http://localhost:8080域的请求是支持跨域的。
maxAge:表示探测请求的有效期。对于DELETE,PUT请求或者自定义信息的请求,在执行过程中先发送探测请求,探测请求不用每次都发送,配置有效期,在有效期过了之后才会发送探测请求。
alloweHeaders:表示允许的请求头,*表示所有的请求头都被允许。
4,文件上传
Java中的文件上传一共涉及到两个组件,
一个是CommonsMultipartResolver,另一个是StandardServletMultipartResolver,
其中CommonsMultipartResolver使用commons-fileupload来处理multipart请求,而StandardServletMultipartResolver则是基于Servlet3.0来处理multipart请求的。因此若使用StandardServletMultipartResolver,则不需要添加额外的jar包。
Tomcat7.0开始就支持Servlet3.0了,而Spring Boot2.0.4内嵌的Tomcat为Tomcat8.5.32,因此可以直接使用StandardServletMultipartResolver。如果开发者没有提供MultipartResolver,那么默认采用的MultipartResolver就是StandardServletMultipartResolver。因此,在Spring Boot中上传文件甚至可以做到零配置。
4.1,单文件上传
package com.springboot.ysy.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@RestController
public class FileUploadController {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
@PostMapping("/upload")
public String upload(MultipartFile uploadFile, HttpServletRequest req){
//规划上传文件的保存路径为项目运行目录下的uploadFile文件夹,并在文件中通过日期对所上传文件归类保存
String realPath = req.getSession().getServletContext().getRealPath("/uploadFile/");
String format = sdf.format(new Date());
File folder = new File(realPath+format);
if(!folder.isDirectory()){
folder.mkdirs();
}
//重命名文件
String oldName = uploadFile.getOriginalFilename();
String newName = UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."),oldName.length());
try {
//文件保存
uploadFile.transferTo(new File(folder,newName));
String filePath = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort()+"/uploadFile/"+format+newName;
return filePath;
}catch (IOException e){
e.printStackTrace();
}
return "上传失败";
}
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFile" value="请选择文件"/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
如果还需要对文件上传的细节进行配置,在application.properties中进行配置。
#是否开启文件上传支持,默认为true spring.servlet.multipart.enabled=true #文件写入磁盘的阀值,默认为0 spring.servlet.multipart.file-size-threshold=0 #上传文件的临时保存位置 spring.servlet.multipart.location=E:\\temp #上传的单个文件的最大大小,默认为1MB spring.servlet.multipart.max-file-size=1MB #多个文件上传时的总大小,默认10MB spring.servlet.multipart.max-request-size=10MB #表示文件是否延迟解析,默认为false spring.servlet.multipart.resolve-lazily=false
4.2,多文件上传
多文件上传和单文件上传基本一致,首先修改HTML文件,然后修改控制器。将上传一个改成,循环上传。如果上传文件超过application.properties设定的文件大小,发出异常,然后被上面程序捕获。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFiles" value="请选择文件" multiple/>
<input type="submit" value="上传"/>
</form>
</body>
</html>package com.springboot.ysy.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@RestController
public class FileUploadController {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
@PostMapping("/uploads")
public String upload(MultipartFile[] uploadFiles, HttpServletRequest req){
for (MultipartFile uploadFile : uploadFiles) {
String realPath = req.getSession().getServletContext().getRealPath("/uploadFile/");
System.out.println(realPath);
String format = sdf.format(new Date());
File folder = new File(realPath + format);
if (!folder.isDirectory()) {
folder.mkdirs();
}
String oldName = uploadFile.getOriginalFilename();
String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."), oldName.length());
try {
uploadFile.transferTo(new File(folder, newName));
String filePath = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + "/uploadFile/" + format + newName;
System.out.println(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
return "上传失败!";
}
}
5,@ControllerAdvice
@ControllerAdvice就是@Controller的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBuinder使用。
5.1,全局异常处理
@ControllerAdvice结合@ExceptionHandler定义全局异常捕获机制。
package com.springboot.ysy.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@ControllerAdvice
public class uploadException {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public void uploadException(MaxUploadSizeExceededException e, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("上传文件大小超出限制");
out.flush();
out.close();
}
}
如果想返回视图,采用以下方法:在templates目录下创建error.html,添加thymeleaf依赖。
package com.springboot.ysy;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
@ControllerAdvice
public class uploadException {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ModelAndView uploadException(MaxUploadSizeExceededException e) throws IOException {
ModelAndView mv = new ModelAndView();
mv.addObject("msg","上传文件超出限制");
mv.setViewName("error");
return mv;
}
}<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>
5.2,添加全局数据
package com.example.demo;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalConfig {
@ModelAttribute(value="info")
public Map<String ,String >userInfo(){
HashMap<String,String> map = new HashMap<>();
map.put("username","罗贯中");
map.put("gender","男");
return map;
}
}
在全局配置中添加userInfo方法,返回一个map。该方法有一个注解@ModelAttribute,其中的value属性表示这条返回数据的key,而方法的返回值是返回数据的value;
此时在任意请求的Controller中,通过方法参数中的Model都可以获取info的数据。
package com.example.demo;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@RestController
public class helloController {
@GetMapping("/hello")
public void hello(Model model){
Map<String,Object> map = model.asMap();
Set<String> keySet = map.keySet();
Iterator<String> iterator = keySet.iterator();
while(iterator.hasNext()){
String key = iterator.next();
Object value = map.get(key);
System.out.println(key+">>>>>>"+value);
}
}
}
6,自定义错误页面
6.1,静态页面
创建resource/static/error,然后创建404.html,500.html(内容自定)。
6.2,动态页面
创建resource/templates/error,然后创建4xx.html,5xx.html。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="2">
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${#messages}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
</table>
</body>
</html>