multipart
项目结构
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── yww
│ └── UploadsController.java
├── resources
│ └── application.properties
└── webapp
├── index.jsp
└── WEB-INF
├── applicationContext.xml
├── dispatcher-servlet.xml
├── jsp
│ └── uploads.jsp
└── web.xml
最简的设置只需要web.xml
中配置,和UploadsController.java
使用@RequestPart
即可。
multipart解析器
DispatcherServlet
并没有实现任何解析multipart请求数据的功能。它将该任务委托给了Spring中MultipartResolver
接口实现,通过该实现类解析multipart请求中的内容。
PS: Part
方式不需要配置MultipartResolver
。
Spring内置2个MultipartResolver
实现:
CommonsMultipartResolver
:使用Jakarta Commons FileUpload解析multipart请求。(不建议使用)StandardServletMultipartResolver
:依赖于Servlet 3.0对multipart请求的支持。(需Spring 3.1以上版本)
配置multipart解析器
最简单的配置,只需声明为Bean即可。(使用Part
可省)
@Bean
public MultipartResolver multipartResolver() throws IOException {
return new StandardServletMultipartResolver();
}
更多的设置。我们需要在Servlet
中配置,使用web.xml
配置比较直观简便。指定了上传位置,文件大小,请求大小。
[web.xml
]
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>/tmp/uploads</location>
<max-file-size>2048000</max-file-size>
<max-request-size>4096000</max-request-size>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!-- <url-pattern>*.form</url-pattern>-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
使用java配置,在实现
WebApplicationInitializer
类的onStartup()
方法中,或继承AbstractAnnotationConfigDispatcherServletInitializer
类的customizeRegistration
方法中设置registration.setMultipartConfig()
配置。
处理multipart请求
表单
设置enctype
为`multipart/form-data’,这会告诉浏览器以multipart数据的形式提交表单。
[WEB-INF/jsp/uploads.jsp
]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>uploads</h1>
<form method="post" enctype="multipart/form-data" >
<input type="file" name="img" />
<input type="submit" />
</form>
</body>
</html>
控制器
@RequestPart
指定请求中对应part数据。如果提交表单时没有选择文件,那么这个数组会是空,而不是null。
[UploadsController.java
]
package com.yww;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import javax.servlet.http.Part;
import java.io.IOException;
@Controller
public class UploadsController {
@RequestMapping(value = "/uploads", method = RequestMethod.GET)
public String uploads(){
return "uploads";
}
@RequestMapping(value = "/uploads", method = RequestMethod.POST)
// public String processImage(@RequestPart("img") byte[] img) {
public String processImage(@RequestPart("img") Part img) {
try {
img.write("/tmp/" + img.getSubmittedFileName());
} catch (IOException e) {
e.printStackTrace();
}
return "uploads";
}
}
web.xml
和UploadsController.java
中都设置了写入的路径,但最后是以控制器中的为准。
使用上传文件的原始byte[]
比较简单但功能有限。因此,Spring还提供了2种接口处理:Part
,MultipartFile
,二者方法十分相似。
html页面
[uploads.jsp
]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>uploads</h1>
<form method="post" enctype="multipart/form-data" >
<input type="file" name="img" />
<input type="submit" />
</form>
</body>
</html>
异常
不管是否正常运行,Servlet请求的输出都是一个Servlet响应。如果在请求处理的时候出现了异常,那它的输出依然会是Servlet响应。异常必须要以某种方式转换为响应。
Spring提供了多种方式将异常转换为响应:
- 特定的Spring异常会自动映射为指定的HTTP状态码。
- 异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码。
- 在方法上可以添加@ExceptionHandle注解,使其用来处理异常。
项目结构
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── yww
│ ├── UserController.java
│ ├── User.java
│ ├── UserNotFoundException.java
│ └── UserNotFoundHandler.java
├── resources
│ └── application.properties
└── webapp
├── index.jsp
└── WEB-INF
├── applicationContext.xml
├── dispatcher-servlet.xml
├── jsp
│ └── error
│ └── usernotfound.jsp
└── web.xml
异常映射HTTP状态码
Spring异常 | HTTP状态码 |
---|---|
BindException | 400 - Bad Request |
ConversionNotSupportedException | 500 - Internal Server Error |
HttpMediaTypeNotAcceptableException | 406 - Not Acceptable |
HttpMediaTypeNotSupportedException | 415 - Unsupported Media Type |
HttpMessageNotReadableException | 400 - Bad Request |
HttpMessageNotWritableException | 500 - Internal Server Error |
HttpRequestMethodNotSupportedException | 405 - Method Not Allowed |
MethodArgumentNotValidException | 400 - Bad Request |
MissingServletRequestParameterException | 400 - Bad Request |
MissingServletRequestPartException | 400 - Bad Request |
TypeMismatchException | 400 - Bad Request |
以上异常,一般会由Spring自身抛出。
如果需要自定义异常,并将其映射到HTTP状态码上,只需要使用@ResponseStatus
注解自定义的异常类即可。使用时,在需要的地方抛出即可。
[UserNotFoundException.java
]
package com.yww;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="User Not Found")
public class UserNotFoundException extends RuntimeException {
}
在引入@ResponseStatus
注解之后,如果控制器方法抛出这个异常的话,响应将会具有404状态码
。
异常处理
为了处理异常情况,可以在控制器里添加一个带@ExceptionHandler
注解的方法,当指定异常抛出时,它会处理。
[UserController.java
]
package com.yww;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(method = RequestMethod.GET)
public String printHome(ModelMap model){
// String username = "yww";
String username = "yww_no";
if(username == "yww"){
System.out.println(username);
}else {
throw new UserNotFoundException();
}
return "home";
}
@ExceptionHandler(UserNotFoundException.class)
public String handleUserNotFound(){
return "error/usernotfound"; // 视图
}
}
PS:这有一个问题,如果每个控制器都需要使用这个异常处理,则需要每个控制器都写上这个@ExceptionHandler
的方法,或者继承一个父类。Spring提供了一个更好的解决方式,控制器通知
用于处理所有控制器中的异常
,初始化
,模型属性
。(如下)
控制器通知
控制器通知(controller advice)是任意带有@ControllerAdvice
注解的类,这个类会包含一个或多个如下类型的方法:
@ExceptionHandler
标注的方法。@InitBinder
标注的方法。@ModelAttribute
标注的方法。
由此可见,控制器通知不仅可以作为异常的集中处理,还有其它2种功能使用。
在带有@ControllerAdvice
注解的类中,以上所述的方法会运用到整个应用程序所有控制器带有@RequestMapping
注解的方法上。
@ControllerAdvice
本身已经使用了@Component
。
[UserNotFoundHandler.java
]
package com.yww;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class UserNotFoundHandler {
@ExceptionHandler(UserNotFoundException.class)
public String handleUserNotFound(){
return "error/usernotfound"; // 视图
}
}
跨重定向请求传递数据
在处理完POST请求后,最佳实践是执行一下重定向。(避免用户点击刷新或返回,导致客户端重新执行危险的POST请求。)
一般来说,当一个处理器方法完成后,该方法所指定的模型数据将会复制到请求中,作为请求中的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所以在转发过程中属性能够得以保存。
但是,当控制器的结果是重定向的话,原始的请求就结束了,并且会发起一个新的GET请求。原始请求中所带的模型数据也就消亡了。
于是,对于重定向来说,模型不能用来传递数据。可以使用其它方案:
- 使用URL模板以路径变量或查询参数的形式传递数据。
- 通过flash属性发送数据。
url传递数据
最简单的就是用过拼接字符串。但当构建URL或SQL查询时,使用字符串连接很危险。
return "redirect:/user/" + "username";
使用占位符解决,{username}
作为占位符填充到URL模板中,而模型中的id
属性没有匹配到URL中的占位符,所以它会自动以查询参数的形式附加到重定向URL上。
[UserRedirectUrlController.java
]
package com.yww;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class UserRedirectUrlController {
@RequestMapping("/user_re_url")
public String getUser(Model model){
String username = "yww";
int id = 1001;
model.addAttribute("username", username);
model.addAttribute("id", id);
return "redirect:/user/{username}";
}
}
flash属性传递数据
使用路径重定向传递数据,只能发送字符串和数字这样简单的数据,如果需要发送如对象的数据,需要使用flash
。
这将会把数据放到会话中,再重定向后取出,我们再负责清除掉。Spring提供了通过RedirectAttributes
设置flash
属性的方法。它是Model
的子接口。
[UserRedirectFlashController.java
]
package com.yww;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class UserRedirectFlashController {
@RequestMapping("/user_re_flash")
public String postUser(RedirectAttributes model){
String username = "yww";
User user = new User();
user.username = "yww";
user.id = 1001;
model.addAttribute("username", username);
model.addFlashAttribute("user", user);
return "redirect:/user/{username}";
}
}
下一个请求的控制器如何取得flash的数据。
[ShowUserController.java
]
package com.yww;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class UserRedirectFlashController {
@RequestMapping("/user_re_flash")
public String postUser(RedirectAttributes model){
String username = "yww";
User user = new User();
user.username = "yww";
user.id = 1001;
model.addAttribute("username", username);
model.addFlashAttribute("user", user);
return "redirect:/user/{username}";
}
}