Spring MVC 高级功能
异常处理
简单异常处理器
创建 ExceptionController 类
ExceptionController.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.itheima.controller;
import com.itheima.exception.MyException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList;
@Controller public class ExceptionController { @RequestMapping("showNullPointer") public void showNullPointer() { ArrayList<Object> list = new ArrayList<>(); System.out.println(list.get(2)); } }
|
配置 SimpleMappingExceptionResolver
spring-mvc.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.itheima.controller"/> <mvc:annotation-driven/> <mvc:resources mapping="/js/**" location="/js/"/> <bean class= "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings"> <props> <prop key="java.lang.NullPointerException"> nullPointerExp.jsp </prop> <prop key="IOException">IOExp.jsp</prop> </props> </property> </bean> <bean class= "org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
|
创建异常处理页面
nullPointerExp.jsp1 2 3 4 5 6 7
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>空指针异常处理页面</title></head> <body> 空指针异常处理页面-----${exp} </body> </html>
|
自定义异常处理器
创建自定义异常类
MyException.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.itheima.exception;
public class MyException extends Exception { private String message;
public MyException(String message) { super(message); this.message = message; }
@Override public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; } }
|
修改 ExceptionController 类
ExceptionController.java1 2 3 4
| @RequestMapping("addData") public void addData() throws MyException { throw new MyException("新增数据异常!"); }
|
创建自定义异常处理器
MyExceptionResolver.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.itheima.controller;
import com.itheima.exception.MyException; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer;
@Component public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String msg; if (ex instanceof MyException) { msg = ex.getMessage(); } else { Writer out = new StringWriter(); PrintWriter s = new PrintWriter(out); ex.printStackTrace(s); String sysMsg = out.toString(); msg = "网络异常!"; } ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", msg); modelAndView.setViewName("error.jsp"); return modelAndView; } }
|
创建异常处理页面
error.jsp1 2 3 4 5 6 7
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>异常处理页面</title></head> <body> ${msg} </body> </html>
|
异常处理注解
创建 ExceptionAdvice 类
ExceptionAdvice.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.itheima.controller;
import com.itheima.exception.MyException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
@ControllerAdvice public class ExceptionAdvice { @ExceptionHandler(MyException.class) public ModelAndView doMyException(MyException ex) throws IOException { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", ex.getMessage()); modelAndView.setViewName("error.jsp"); return modelAndView; }
@ExceptionHandler(Exception.class) public ModelAndView doOtherException(Exception ex) throws IOException { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg", "网络异常!"); modelAndView.setViewName("error.jsp"); return modelAndView; } }
|
值得注意的是,@ControllerAdvice 注解的类中定义的
@ExceptionHandler 方法会优先于
HandlerExceptionResolver 处理异常。
拦截器
自定义拦截器
创建 MyInterceptor 类
MyInterceptor.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.itheima.interceptor;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("MyInterceptor...preHandle"); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("MyInterceptor...postHandle"); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("MyInterceptor...afterCompletion"); } }
|
配置 MyInterceptor 拦截器
spring-mvc.xml1 2 3 4 5
| <mvc:interceptors> <bean class="com.itheima.interceptor.MyInterceptor"/> </mvc:interceptors>
|
其中,preHandle
方法在控制器方法执行前被调用,postHandle
方法在控制器方法正常执行后被调用,afterCompletion
方法在整个请求完成后被调用。
对于大型项目,可能需要多个拦截器来处理不同的请求,这时可以在
<mvc:interceptors> 标签中配置多个
<bean> 来定义多个拦截器。
我们新增一个拦截器
MyInterceptor2.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.itheima.interceptor;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class MyInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("MyInterceptor2...preHandle"); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { System.out.println("MyInterceptor2...postHandle"); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("MyInterceptor2...afterCompletion"); } }
|
配置 MyInterceptor2 拦截器
spring-mvc.xml1 2 3 4 5 6 7
| <mvc:interceptors> <bean class="com.itheima.interceptor.MyInterceptor"/> <bean class="com.itheima.interceptor.MyInterceptor2"/> </mvc:interceptors>
|
当有多个拦截器时,preHandle
方法会按照配置的顺序依次执行,而 postHandle 和
afterCompletion 方法则会按照相反的顺序执行。
文件上传/下载
fileload.jsp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件上传和下载</title> <script src="${ pageContext.request.contextPath }/js/jquery-3.6.0.js" type="text/javascript"></script> </head> <body> <table border="1"> <tr> <td width="200" align="center">文件上传${msg}</td> <td width="300" align="center">下载列表</td> </tr> <tr> <td height="100"> <form action="${pageContext.request.contextPath}/fileload" method="post" enctype="multipart/form-data"> <input type="file" name="files" multiple="multiple"><br/> <input type="reset" value="清空"/> <input type="submit" value="提交"/> </form> </td> <td id="files"></td> </tr> </table> </body> <script> $(document).ready(function () { var url = "${pageContext.request.contextPath }/getFilesName"; $.get(url, function (files) { var files = eval('(' + files + ')'); for (var i = 0; i < files.length; i++) { $("#files").append("<li>" + "<a href=${pageContext.request.contextPath }" + "" + "\\" + "download?filename=" + files[i].name + ">" + files[i].name + "</a></li>"); } }) }) </script> </html>
|
通过表单上传文件,Spring MVC 提供了 MultipartFile
接口来处理文件上传。我们可以在控制器方法中使用
MultipartFile 参数来接收上传的文件。
需要配置 MultipartResolver 以启用文件上传功能:
spring-mvc.xml1 2 3 4 5 6 7
| <bean id="multipartResolver" class= "org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <property name="maxUploadSize" value="2097152"/> </bean>
|
由于依赖 Apache Commons FileUpload
组件来处理文件上传,因此需要在项目中添加相关依赖:
pom.xml1 2 3 4 5
| <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
|
创建资源类
Resource.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.itheima.pojo;
public class Resource { private String name;
public Resource() { }
public Resource(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
创建 JSON 文件工具类
JSONFileUtils.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.itheima.utils;
import org.apache.commons.io.IOUtils;
import java.io.FileInputStream; import java.io.FileOutputStream;
public class JSONFileUtils { public static String readFile(String filepath) throws Exception { FileInputStream fis = new FileInputStream(filepath); return IOUtils.toString(fis); }
public static void writeFile(String data, String filepath) throws Exception { FileOutputStream fos = new FileOutputStream(filepath); IOUtils.write(data, fos); } }
|
创建文件管理控制器
FileController.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| package com.itheima.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.itheima.pojo.Resource; import com.itheima.utils.JSONFileUtils; import org.apache.commons.io.FileUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List;
@Controller public class FileController {
@RequestMapping("fileload") public String fileLoad(MultipartFile[] files, HttpServletRequest request) throws Exception { String path = request.getServletContext().getRealPath("/") + "files/"; ObjectMapper mapper = new ObjectMapper(); if (files != null && files.length > 0) { for (MultipartFile file : files) { String filename = file.getOriginalFilename(); ArrayList<Resource> list = new ArrayList<>(); String json = JSONFileUtils.readFile(path + "/files.json"); if (json.length() != 0) { list = mapper.readValue(json, new TypeReference<List<Resource>>() { }); for (Resource resource : list) { if (filename.equals(resource.getName())) { String[] split = filename.split("\\."); filename = split[0] + "(1)." + split[1]; } } } String filePath = path + filename; file.transferTo(new File(filePath)); list.add(new Resource(filename)); json = mapper.writeValueAsString(list); JSONFileUtils.writeFile(json, path + "/files.json"); } request.setAttribute("msg", "(上传成功)"); return "forward:fileload.jsp"; } request.setAttribute("msg", "(上传失败)"); return "forward:fileload.jsp"; }
@ResponseBody @RequestMapping(value = "/getFilesName", produces = "text/html;charset=utf-8") public String getFilesName(HttpServletRequest request, HttpServletResponse response) throws Exception { String path = request.getServletContext(). getRealPath("/") + "files/files.json"; String json = JSONFileUtils.readFile(path); return json; }
public String getFileName(HttpServletRequest request, String filename) throws Exception { Base64.Encoder base64Encoder = Base64.getEncoder(); String agent = request.getHeader("User-Agent"); if (agent.contains("Firefox")) { filename = "=?UTF-8?B?" + new String(base64Encoder.encode(filename.getBytes(StandardCharsets.UTF_8))) + "?="; } else { filename = URLEncoder.encode(filename, "UTF-8"); } return filename; }
@RequestMapping("/download") public ResponseEntity<byte[]> fileDownload(HttpServletRequest request, String filename) throws Exception { String path = request.getServletContext().getRealPath("/files/"); filename = new String(filename.getBytes("ISO-8859-1"), "UTF-8"); File file = new File(path + File.separator + filename); HttpHeaders headers = new HttpHeaders(); filename = this.getFileName(request, filename); headers.setContentDispositionFormData("attachment", filename); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK); } }
|