1 替代配置 1.1 自定义 DispatcherServlet 除了三个必须重载的抽象方法,AbstractAnnotationConfigDispatcherServletInitializer 还有很多方法可以重载,以实现额外配置。
例如,通过对 customizeRegistration() 的重写,就可以对 DispatcherServlet 进行额外的配置。
@Override protected void customizeRegistration (Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement ("/tmp/spittr/uploads" )); }
ServletRegistration.Dynamic 作为入参,可以做很多事情,比如调用 setLoadOnStartup() 来设置加载时优先级,调用 setInitParameter() 来设置初始化参数,调用 setMultipartConfig() 来设置 Servlet3.0 的多路支持。
1.2 添加额外的 servlet 和 filter 实现 WebApplicationInitializer 接口是在注册 servlet、filter、listener 时比较推荐的方式
public class MyServletInitializer implements WebApplicationInitializer { @Override public void onStartup (ServletContext servletContext) throws ServletException { Dynamic myServlet = servletContext.addServlet("myServlet" , MyServlet.class); myServlet.addMapping("/custom/**" ); javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter" , MyFilter.class); filter.addMappingForUrlPatterns(null , false , "/custom/*" ); } }
如仅需要注册一个 filter 并将其映射到 DispatcherServlet,重写 AbstractAnnotationConfigDispatcherServletInitializer 的 getServletFilters() 方法是一个捷径。
@Override protected Filter[] getServletFilters() { return new Filter [] { new MyFilter () }; }
通过 getServletFilters() 返回的 filter 会自动地映射到 DispatcherServlet。
2 处理 multipart 表单数据 Multipart/form-data 将表单分割成独立的部分,每个部分都有各自的类型,可以处理二进制数据
2.1 配置 multipart 解析器 Spring 提供了两种 MultipartResolver 实现类:
CommonsMultipartResolver:使用 Jakarta Commons FileUpload 来解析 multipart 请求;
StandardServletMultipartResolver:依靠 Servlet 3.0 支持来解析(Spring 3.1及以上);
推荐 StandardServletMultipartResolver,它使用 servlet 容器中现有的支持,并且不需要其他附加的项目依赖。
@Bean public MultipartResolver multipartResolver () { return new StandardServletMultipartResolver (); }
配置 multipart 详情 MultipartConfigElement
继承自 WebMvcConfigurerAdapter 的 servlet 初始化类中配置的 DispatcherServlet,在 servlet 注册时通过调用 setMultipartConfig() 方法来配置
DispatcherServlet ds = new DispatcherServlet ();Dynamic registration = context.addServlet("appServlet" , ds);registration.addMapping("/" ); registration.setMultipartConfig(new MultipartConfigElement ("/tmp/spittr/uploads" ));
继承自 AbstractAnnotationConfigDispatcherServletInitializer 的 servlet 初始化类,重写 customizeRegistration() 方法来进行配置
@Override protected void customizeRegistration (Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement ("/tmp/spittr/uploads" )); }
2.2 处理 multipart 请求 1)表单 标签 enctype=“multipart/form-data” 属性
type=“file”
<form method="POST" th:object="${spitter}" enctype="multipart/form-data"> ... <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" /> ...
2)controller 使用 @RequestPart 注解 byte 数组
@PostMapping("/register") public String processRegistration (@RequestPart("profilePicture") byte [] profilePicture, @Valid Spitter spitter,Errors errors) {}
3)接收 MultipartFile Spring 提供了 MultipartFile 接口获取富对象
@PostMapping("/register") public String processRegistration (@RequestPart("profilePicture") MultipartFile profilePicture, ...) {}
MultipartFile 提供获取上传文件的方法,同时提供了很多其他方法,比如原始文件名称、大小和内容类型等。另外还提供了一个 InputStream 可以将文件数据作为数据流读取。
transferTo() 写入到文件系统
profilePicture.transferTo(new File ("/data/spittr/" + profilePicture.getOriginalFilename()));
4)接收 Part Servlet 3.0 的容器上,可以选择 Part,大多数情况下 Part 接口和 MultipartFile 没什么区别。
@RequestMapping(value = "/register", method = RequestMethod.POST) public String processRegistration (@RequestPart("profilePicture") Part profilePicture, ...) {
如果使用 Part 作为参数,就不再需要配置 StandardServletMultipartResolverbean,它只需在使用 MultipartFile 时进行配置。
3 异常处理 servlet 请求的输出只能是 servlet 响应。如果请求出现异常,需要将异常转换为 servlet 响应。
Spring 提供了一些将异常转化为响应的方法:
某些 Spring 异常会自动的映射为特定的 HTTP 状态码;
使用@ResponseStatus
注解将一个异常映射为 HTTP 状态码;
使用ExceptionHandler
注解的方法可以用来处理异常
3.1 @ResponseStatus 内置映射之外,可用@ResponseStatus
注解将一个异常映射为 HTTP 状态码
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found") public class SpittleNotFoundException extends Exception {}
3.2 @ExceptionHandler @ExceptionHandler 注解的方法在有指定异常抛出时执行,在同一个 controller 里通用
@ExceptionHandler(DuplicateSpittleException.class) public String handleDuplicateSpittle () { return "error/duplicate" ; }
3.3 @ControllerAdvice 控制器增强类,即使用@ControllerAdvice
进行注解的类,由以下方法构成:
@ExceptionHandler 注解的
@InitBinder 注解的
@ModelAttribute 注解的
应用于所有 controller 的所有 @RequestMapping 注解的方法。
@ControllerAdvice public class ControllerExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) public String handle (ValidationException exception) { log.warn("bad request, " + exception.getMessage()); return "bad request, " + exception.getMessage(); } @ExceptionHandler({BaseException.class}) public ResponseEntity<?> handleBaseException(final Exception ex, WebRequest request) { BaseException baseEx = (BaseException) ex; return handleExceptionInternal(ex, ErrorResponse.of(baseEx), new HttpHeaders (), baseEx.getHttpStatus(), request); } @Override protected ResponseEntity<Object> handleMethodArgumentNotValid (MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, ErrorResponse.of(ex), headers, HttpStatus.BAD_REQUEST, request); } @Override protected ResponseEntity<Object> handleMissingServletRequestParameter (MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, new ErrorResponse (ex.getMessage(), "missing_request_parameter" ), headers, status, request); } @JsonInclude(JsonInclude.Include.NON_NULL) public static class ErrorResponse { private final String message; private final String code; List<String> errors; private ErrorResponse (String message, String code) { this .code = code; this .message = message; } private ErrorResponse (String message, String code, List<String> errors) { this .message = message; this .code = code; this .errors = errors; } public static ErrorResponse of (BaseException ex) { return new ErrorResponse (ex.getMessage(), ex.getCode()); } public static ErrorResponse of (MethodArgumentNotValidException ex) { List<String> errors = new ArrayList <>(); for (FieldError error : ex.getBindingResult().getFieldErrors()) { errors.add(error.getField() + ": " + error.getDefaultMessage()); } for (ObjectError error : ex.getBindingResult().getGlobalErrors()) { errors.add(error.getObjectName() + ": " + error.getDefaultMessage()); } return new ErrorResponse ("输入不合法" , "bad_request" , errors); } public static ErrorResponse of (Map<String, Object> errorAttributes) { return new ErrorResponse ((String) errorAttributes.get("message" ), (String) errorAttributes.get("error" )); } public String getMessage () { return message; } public String getCode () { return code; } public List<String> getErrors () { return errors; } } }
BaseException
@Data public abstract class BaseException extends RuntimeException { protected HttpStatus httpStatus = HttpStatus.BAD_REQUEST; protected String code = "unknown_error" ; public BaseException (String message) { super (message); } public BaseException (String message, Throwable cause) { super (message, cause); } }
InternalServerErrorException
public class InternalServerErrorException extends BaseException { { httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; code = "internal_server_error" ; } public InternalServerErrorException () { super ("系统内部发生未知异常" ); } public InternalServerErrorException (String message) { super (message); } public InternalServerErrorException (String message, Throwable cause) { super (message, cause); } }
4 跨 redirect 传递数据 一般的,处理函数结束后,方法中的 model 数据都会作为 request 属性复制到 request 中,并且 request 会传递到视图中进行解析。
forward 时,使用同一个 request,request 属性保留。
redirect 时,终止老 request,开启新 request。request 属性清空。
redirect 时不能使用 model 传递数据了。但是还有其他方法:
将数据转换为路径参数或者查询参数
在 flash 属性中发送数据
4.1 使用 URL 模版重定向 使用路径参数和查询参数传递简单数据
@PostMapping("/register") public String processRegistration (Spitter spitter, Model model) { spitterRepository.save(spitter); model.addAttribute("username" , spitter.getUsername()); model.addAttribute("spitterId" , spitter.getId()); return "redirect:/spitter/{username}" ; }
username 作为路径参数,spitterId 转为查询参数
4.2 使用 flash 属性 Spring 通过 Model 的子接口 RedirectAttributes 设置 flash 属性。
重定向前,所有的 flash 属性都会拷贝到 session 中
@PostMapping("/register") public String processRegistration (Spitter spitter, RedirectAttributes model) { spitterRepository.save(spitter); model.addAttribute("username" , spitter.getUsername()); model.addFlashAttribute("spitter" , spitter); return "redirect:/spitter/{username}" ; }
重定向后,存储在 session 中的 flash 属性会从 session 中移出到 model 中。
@GetMapping("/{username}") public String showSpitterProfile (@PathVariable("username") String username, Model model) { if (!model.containsAttribute("spitter" )) { Spitter spitter = spitterRepository.findByUsername(username); model.addAttribute(spitter); } return "profile" ; }