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=true
package 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>