一、Restful请求格式
1. 介绍
Rest(Representational State Transfer:表现层状态转移)是一种软件架构风格,其核心是面向资源的一种设计。
2. 正常使用
Restful要求在当前的url地址中直接嵌套请求数据。
restful中要求使用不同的请求方式来标记对资源的操作方式,达到调用不同的后台功能方法来处理请求的目的。
/**
* @RequestMapping注解可以接收任意请求方式的请求
* @GetMapping("地址"):接收GET请求,一般用在查询方法上
* @DeleteMapping("地址"):接收DELETE请求,一般用在删除方法上
* @PostMapping("地址"):接收POST请求,一般用户在新增上
* @PutMapping("地址"):接收PUT请求,一般用在修改上
*/
//查询用户信息
@GetMapping("/user/{id}")
public String selUser(@PathVariable Integer id){
System.out.println("用户ID为:"+id);
return "success.jsp";
}
//删除用户信息
@DeleteMapping("/user/{id}")
public String delUser(@PathVariable Integer id){
System.out.println("用户ID为:"+id);
return "success.jsp";
}
//新增用户信息
@PostMapping("/user/{id}/{name}/{age}")
public String addUser(@PathVariable Integer id,@PathVariable String name,@PathVariable Integer age){
System.out.println("id = " + id + ", name = " + name + ", age = " + age);
return "success.jsp";
}
//修改用户信息
@PutMapping("/user/{id}/{name}")
public String updateUser(@PathVariable Integer id,@PathVariable String name){
System.out.println("id = " + id + ", name = " + name);
return "success.jsp";
}
3. 使用Restful显示页面
使用Restful只写一个控制器。
@RequestMapping("/{yemian}")
public String showPage(@PathVariable String yemian){
return "/WEB-INF/page/"+yemian+".jsp";
}
二、@ResponseBody注解
1. @ResponseBody介绍
@ResponseBody注解是类或方法级注解。
当方法上添加@ResponseBody注解后,控制单元方法返回值将不再被视图解析器进行解析|不会使用转发。而是把返回值放入到响应流中进行响应。
直接在方法上添加上@ResponseBody,Spring MVC会把返回值设置到响应流中。 等效于直接使用PrintWriter对象进行打印。
@RequestMapping("/demo1")
@ResponseBody
public String demo1(){
return "aa";
}
2. 设置响应内容类型
在使用@ResponseBody注解时,只要返回值类型不是类或Map或List等满足键值对类型。Spring MVC 都会设置响应内容类型为text/html;charset=ISO-8859-1。
想要改变@ResonseBody注解的响应内容类型(Content-Type)只能通过@RequestMapping的produces属性进行设置。
@RequestMapping(value="/demo1",produces = "text/html;charset=utf-8")
@ResponseBody
public String demo1() {
return "张三";
}
3. 自动转换为JSON字符串
@ResponseBody注解可以把控制单元返回值自动转换为JSON字符串。主要完成下面几个事情:
(1)判断返回值是否为JavaBean、JavaBean数组、List<JavaBean类型>、Map等满足键值对的类型。
(2)如果满足键值对类型,会使用Jackson把对象转换为JSON字符串,设置到响应流中。同时会设置响应内容类型(Content-Type)为application/json;charset=utf-8
因为Spring MVC默认使用Jackson作为JSON转换工具,所以必须保证项目中存在Jackson的依赖。
4. 转换为XML文件
在Spring MVC中支持把返回值转换为XML文件。如果还是使用jackson-databind依赖,默认只能转换返回值为类类型的控制单元,返回值为List是无法转换为XML的,同时还要求实体类上必须有@XmlRootElement,才能转换。
5. @RestController注解
对于页面中使用前端框架时的项目。例如页面时通过:EasyUI、BootStrap、Vue等前端框架进行编写时,客户端向服务端发送的请求都是异步Ajax(或类似Ajax的异步请求)。对于这样的项目,控制器中所有的方法都包含@ResponseBody注解。
@RestController // 此处换成了@RestController,而不是@Controller了
public class DemoController {
// 下面所有方法都不写@ResponseBody注解
@RequestMapping("/demo")
public Map<String,Object> demo(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",18);
return map;
}
三、@RequestBody注解
1. 介绍
@RequestBody注解底层依赖的依然是Jackson工具包,其作用是把客户端传递过来的请求体中JSON或XML数据转换为Map、类、List<类>、List<Map>等类型。
如果希望在单体架构项目中使用@RequestBody注解,需要在客户端中使用Ajax请求,刻意设置请求的内容类型(Content-Type)为JSON或XML。
2. 修改请求内容类型
如果希望修改请求内容类型,可以使用HTML的<form>
中enctype属性或使用Ajax中contentType属性进行设置。
<form>
的enctype属性一般只有在文件上传时才会修改,所以希望传递特定类型请求参数内容时,都是通过Ajax进行请求。
$.ajax({
url:"testContentType",
contentType:"application/json",// 修改请求内容类型为JSON
data:'{"id":1,"name":"张三"}',// 取值两次必须有单引号,没有单引号无效
type:"post",// 不能是GET类型请求
success:function (data) {
console.log(data);
},
dataType:"json"
});
四、Spring MVC文件上传
1. 文件上传介绍
文件上传就是把客户端的文件上传到服务端进行保存。在文件上传时文件和其他请求参数是在请求体中进行传递。所以不支持GET类型请求。
表单内容类型application/x-www-form-urlencoded不支持传递文件流。所以需要在<form>
的enctype中设置enctype="multipart/form-data"才表示把文件和其他表单参数设置到请求体中。
总结出来,Spring MVC 文件上传有如下几点要求:
(1)客户端:
(1.1) 请求方式必须是POST
(1.2)enctype必须为multipart/form-data
(2)服务端:
(2.1)必须配置MultipartResovler。否则无法解析上传文件的流数据。(<bean>
的id值必须叫做multipartResovler)如果没有配置MultipartResovler不仅仅是文件流数据无法解析,连带着其他表单域数据也无法解析。因为文件流数据和表单数据都在请求体中,不解析的话,文件流数据和表单数据都接收不到。
(2.2)注意文件域的name取值,文件域必须MultipartFile类型接收。且name的取值必须和MultipartFile对象名相同。
配置上传解析器bean
只有配置了MultipartResovler,Spring MVC 才会解析上传文件流数据。
同时<bean>
的id必须叫做multipartResovler,叫其他名字无效。
<!-- 文件上传时,必须配置文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
@Controller
public class PeopleController {
/**
* 文件上传控制单元方法实现
*
* @param name 也可以使用JavaBean接收name的值
* @param address 也可以使用JavaBean接收address的值
* @param photo 名字必须和表单中文件域的name属性值相同
* @return
* @throws IOException transferTo抛出的异常,可以使用try...catch处理异常。示例中为了让代码看起来简洁直接抛出了。
*/
@RequestMapping("/upload")
public String upload(String name, String address, MultipartFile photo) throws IOException {
photo.transferTo(new File("D:/images", photo.getOriginalFilename()));
return "/upload.jsp";
}
}
2. 生成唯一文件名
在上面代码中,保存文件名称时是使用文件上传时的名称进行保存。这样做存在一个问题:如果存在同名文件,后上传文件会覆盖之前文件内容。
所以在文件上传时都会生成一个全局唯一的文件名。常见有两种方式:
(1)时间戳+随机数
(2)UUID
/**
* 文件上传控制单元方法实现
* @param name 也可以使用JavaBean接收name的值
* @param address 也可以使用JavaBean接收address的值
* @param photo 名字必须和表单中文件域的name属性值相同
* @return
* @throws IOException transferTo抛出的异常,可以使用try...catch处理异常。示例中为了让代码看起来简洁直接抛出了。
*/
@RequestMapping("/upload")
public String upload(String name, String address, MultipartFile photo) throws IOException {
// 判断上传文件流是否为空。如果不为空继续执行
if(!photo.isEmpty()) {
// 使用UUID生成文件名称
// String fileName = UUID.randomUUID().toString();
// 使用时间戳+随机数生成文件名
long timeMillis = System.currentTimeMillis();
Random random = new Random();
String fileName = timeMillis + "" + random.nextInt(1000);
// 获取上传时文件名
String oldName = photo.getOriginalFilename();
// 获取上传时文件的扩展名
String suffix = oldName.substring(oldName.lastIndexOf("."));
// 保存文件到D:/images中。必须保存D盘下已经存在images文件夹
photo.transferTo(new File("D:/images",fileName + suffix));
}
return "/upload.jsp";
}
3. 保存文件到当前项目中
保存到项目发布到Tomcat的目录而不是源码目录。在使用Tomcat插件时,target/项目名-版本 目录为项目编译后发布到Tomcat的目录。
@RequestMapping("/upload")
public String upload(String name, String address, MultipartFile photo,HttpServletRequest request) throws IOException {
if(!photo.isEmpty()) {
long timeMillis = System.currentTimeMillis();
Random random = new Random();
String fileName = timeMillis + "" + random.nextInt(1000);
String oldName = photo.getOriginalFilename();
String suffix = oldName.substring(oldName.lastIndexOf("."));
// 获取到当前项目images目录,发布到Tomcat后的绝对路径。
String realPath = request.getServletContext().getRealPath("/images");
System.out.println(realPath);
// 保存到当前项目的images目录中。
photo.transferTo(new File(realPath,fileName + suffix));
}
return "/upload.jsp";
}
4. 限制上传文件大小
在CommonsMultipartResolver中提供了setmaxUploadSize(long)方法,表示设置上传文件的大小。单位是字节byte。默认值为-1,表示无限制。 因为是setter方法,所以可以在配置Bean时直接进行设置注入。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1024"></property>
</bean>
五、Spring MVC文件下载
1. 文件下载介绍
文件下载就是把服务器中的资源下载到本地。
当超链接访问的是浏览器本身能打开的资源。浏览器直接打开。这个特点就是响应头参数Content-Disposition控制的,其默认值为inline,表示能打开就打开,不能打开就下载。
attachment
如果希望所有的文件都是下载,而不是能打开则打开。可以在响应头中设置Content-Disposition参数为attachment。attachment结合filename可以设置下载文件的名称。
需要设置响应头,所以就必须编写一个下载的控制器。
@RequestMapping("/download")
public void download(HttpServletRequest req, HttpServletResponse response, String filename) {
try {
// filename=的值就是客户端看到的下载文件名称
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
File file = new File(req.getServletContext().getRealPath("/images"), filename);
FileInputStream fis = new FileInputStream(file);
ServletOutputStream os = response.getOutputStream();
IOUtils.copy(fis, os);
} catch (IOException e) {
e.printStackTrace();
}
}
实现访问控制器,并传递需要下载文件名称。
<a href="/download?filename=a.png">a.png</a>
2. 文件下载中包含中文名称解决办法
如果文件下载时包含中文名称,需要保证filename=后面的内容是ISO-8859-1编码。如果filename=后面是UTF-8编码且包含中文会乱码。
改写控制器代码,需要反复进行编码转换
@RequestMapping("/download")
public void download(HttpServletRequest req, HttpServletResponse response, String filename) {
try {
// 因为是GET请求,所以要解决请求参数中文乱码问题
String fileNameUtf8 = new String(filename.getBytes("iso-8859-1"), "utf-8");
// 图片名称满足固定格式
String newFilenameUtf8 = "来自尚学堂的"+fileNameUtf8;
String newFilenameISO = new String(newFilenameUtf8.getBytes("utf-8"),"iso-8859-1");
// 此处是ISO-8859-1编码的内容
response.setHeader("Content-Disposition", "attachment;filename=" + newFilenameISO);
// 此处必须是UTF-8解决参数乱码问题的名称
File file = new File(req.getServletContext().getRealPath("/images"), fileNameUtf8);
FileInputStream fis = new FileInputStream(file);
ServletOutputStream os = response.getOutputStream();
IOUtils.copy(fis, os);
} catch (IOException e) {
e.printStackTrace();
}
}