超全SpringMVC知识点!!(万字总结)
文章目录
- 前言
- 1、MVC是什么
- 2、SpringMVC是什么
- 3、SpringMVC请求流程 && 环境搭建
- 3.1 SpringMVC请求流程
- 3.2 搭建环境
- 3.2.1开发环境
- 3.2.2 环境配置步骤
- 4. url地址映射 && 参数绑定
- 4.1 url地址映射之@RequestMapping
- ①、映射单个url
- ②、映射多个url
- ③、映射url到控制器
- ④、通过参数名称映射url
- ⑤、设置请求方式
- 4.2 参数绑定
- ①、基本类型
- ②、包装类型 && 字符串类型
- ③、数组类型 && javaBean类型
- ④、集合类型
- 5. SpringMVC请求转发与重定向
- 5.1 请求域对象设置API
- 5.2 请求重定向
- 5.3 请求转发
- 6. JSON请求
- 6.1配置JSON环境
- 6.2 @ResponseBody注解
- 6.3 @RequestBody注解
- 7. 拦截器
- 7.1 实现HandlerInterceptor接口
- 7.2 继承HandlerInterceptorAdapter类
- 7.3 拦截器链
- 8. 文件上传
- 8.1 环境配置
- 8.2 单文件上传
- 8.3 多文件上传
- 9. 全局异常
- 9.1 SimpleMappingExceptionResolver处理
- 9.2 实现接口 HandlerExceptionResolver定义自己的异常处理器
- 9.3 @ExceptionHandler注解处理
- 9.4 未捕获异常的处理
前言
SpringMVC是一个Spring基于MVC模式开发的一个框架,其底层封装了Servlet,用于简化开发,而MVC是一种思想。虽然现在SpringBoot用的比较多,但是SpringMVC面试也会问到,而且技术层面也有必要整体流程有个了解。
1、MVC是什么
模型(model)-视图(view)-控制器(Controller) ,就是MVC,它通过分离模型、视图、控制器在应用程序中的角色将业务逻辑从界面中解耦。Controller负责接收用户的请求,并调用后台服务(Service和Dao)来处理业务逻辑。View用来将结果显示给用户,不包含任何逻辑(现在前后端分离之后,View层很少用了)。
2、SpringMVC是什么
SpringMVC是Spring家族的web成员,他是基于java的实现了Web MVC思想的请求驱动类型的轻量级web框架。
SpringMVC是服务到工作者思想的实现,前端控制器是DispatcherServlet; 应用控制器拆为处理器映射器(HandlerMapping)进行处理管理和视图解析器(View Resolver)进行视图管理; 支持本地化/国际化(Locale) 解析及文件上传等; 提供灵活的数据验证、格式化和数据绑定机制; 提供了约定大于配置的的编程支持。
SpringMVC优点:
- 代码分层更整洁好看一些
- 天生与Spring框架集成(Ioc、Aop等)
- 可以进行简单单元测试
3、SpringMVC请求流程 && 环境搭建
3.1 SpringMVC请求流程
3.2 搭建环境
3.2.1开发环境
idea2022 + Maven3.8.1 + jdk1.8 + jetty
3.2.2 环境配置步骤
配置pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.xxxx</groupId><artifactId>Springmvc01</artifactId><version>1.0-SNAPSHOT</version><name>Springmvc01</name><packaging>war</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.source>1.8</maven.compiler.source><junit.version>5.8.2</junit.version></properties><dependencies><!-- 测试相关依赖 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>${junit.version}</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>${junit.version}</version><scope>test</scope></dependency><!-- spring web --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.4.RELEASE</version></dependency><!-- spring mvc --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.4.RELEASE</version></dependency><!-- web servlet --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version></dependency></dependencies><build><finalName>springmvc01</finalName><plugins><!-- 编译环境插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.2</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><!-- --><plugin><groupId>org.eclipse.jetty</groupId><artifactId>jetty-maven-plugin</artifactId> <!-- 注意:原图文本可能有拼写错误(jetty-maven-plusin → jetty-maven-plugin) --><version>9.4.27.v20200227</version><configuration><scanIntervalSeconds>10</scanIntervalSeconds> <!-- 扫描间隔(秒) --><httpConnector><port>8080</port> <!-- 端口 --></httpConnector><webAppConfig><contextPath>/springmvc01</contextPath> <!-- 项目路径(URL路径) --></webAppConfig></configuration></plugin></plugins></build>
</project>
注意点:
根访问路径在pom.xml文件中定义
<webAppConfig><contextPath>/springmvc01</contextPath> <!-- 项目路径(URL路径) --></webAppConfig>
配置web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="webApp_ID" version="3.0"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"><!-- 字符编码过滤器 --><filter><description>char encoding filter</description><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- servlet请求分发器 --><servlet><servlet-name>springMvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:servlet-context.xml</param-value></init-param><!-- 表示启动时初始化该Servlet --><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springMvc</servlet-name><!-- 拦截规则:"/" 代表拦截所有请求,"*.do" 拦截所有.do请求 --><url-pattern>/</url-pattern></servlet-mapping></web-app>
配置servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启组件扫描 --><context:component-scan base-package="com.xxxx.springmvc.controller"/><!-- 使用默认 Servlet 响应静态文件 --><mvc:default-servlet-handler/><!-- 开启注解驱动 --><mvc:annotation-driven/><!-- 配置视图解析器 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"id="internalResourceViewResolver"><!-- 前缀:在 WEB-INF 目录下的 jsp 目录下 --><property name="prefix" value="/WEB-INF/jsp/"/><!-- 后缀:以 .jsp 结尾的资源 --><property name="suffix" value=".jsp"/></bean></beans>
项目结构如下:
注意点:
①、项目结构必须严格按照这个来,jsp文件必须放WEB-INF/jsp目录下面,web.xml必须放WEB-INF下。
②、servlet-context.xml文件中 <context:component-scan base-package> 配置的包前缀必须和java目录下包名前缀相同。
jsp页面(不用在意细节,jsp只用于演示不细学):
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%String path = request.getContextPath();String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE html>
<html>
<head><base href="<%=basePath %>"><title>My JSP 'hello.jsp' starting page</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"><meta http-equiv="keywords" content="keyword1, keyword2, keyword3"><meta http-equiv="description" content="This is my page">
</head>
<body>
<!-- EL 表达式接收参数值 -->
${hello}
</body>
</html>
演示代码:
package com.xxxx.springmvc.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class HelloController {/*** 请求地址的映射** @return*/@RequestMapping("/hello")public ModelAndView hello() {ModelAndView modelAndView = new ModelAndView();// 设置数据modelAndView.addObject("hello", "Hello SpringMvc");// 设置视图名称modelAndView.setViewName("hello");return modelAndView;}}
最终效果:
至此,我们成功跑通了Springmvc的环境。
4. url地址映射 && 参数绑定
4.1 url地址映射之@RequestMapping
@RequestMapping方法可以将请求路径和方法绑定,可以声名在类和方法级别。
①、映射单个url
示例代码:
@Controller
public class UrlController {/*** 声名在方法上,映射单个url* @return*/@RequestMapping("/test01")public ModelAndView test01() {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("hello", "test01");modelAndView.setViewName("hello");return modelAndView;}}
运行结果:
②、映射多个url
@RequestMapping({"/test02", "/test03"})public ModelAndView test02_3() {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("hello", "test02_3");modelAndView.setViewName("hello");return modelAndView;}
运行结果:
可以看到都可以访问到。
③、映射url到控制器
其实就是相当于在Controller类上加了一层映射。
@Controller
@RequestMapping("/url")
public class HelloController {/*** 请求地址的映射** @return*/@RequestMapping("/hello")public ModelAndView hello() {ModelAndView modelAndView = new ModelAndView();// 设置数据modelAndView.addObject("hello", "Hello SpringMvc");// 设置视图名称modelAndView.setViewName("hello");return modelAndView;}
}
访问路径多了一个/url。
④、通过参数名称映射url
@RequestMapping(params = "test04")public ModelAndView test04() {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("hello", "test04");modelAndView.setViewName("hello");return modelAndView;}
运行结果:
访问路径改为 ? 后面跟参数
⑤、设置请求方式
由于浏览器默认使用GET方式请求,因此改成POST就无法访问到了。
@RequestMapping(value = "test05", method = RequestMethod.POST)public ModelAndView test05() {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("hello", "test05");modelAndView.setViewName("hello");return modelAndView;}
运行结果:
4.2 参数绑定
①、基本类型
@Controller
public class ParamsController {@RequestMapping("/data01")public void data01(int age, double money) {System.out.println("age: " + age + " money: " + money);}@RequestMapping("/data02")public void data02(@RequestParam(defaultValue = "18") int age, @RequestParam(defaultValue = "20.0") double money) {System.out.println("age: " + age + " money: " + money);}@RequestMapping("/data03")public void data03(@RequestParam(defaultValue = "18", name = "userAge") int age,@RequestParam(defaultValue = "20.0", name = "userMoney") double money) {System.out.println("age: " + age + " money: " + money);}
}
访问/data01的url
http://localhost:8080/springmvc01/data01?age=17&money=18.0
打印结果:
age: 17 money: 18.0
但是如果我们传参的时候没有传完整,就会报空指针null的错误
url:
http://localhost:8080/springmvc01/data01?age=17
因此,/data02模块的@RequestParam注解提供的默认值就可以解决这个问题。
访问/data02的url
http://localhost:8080/springmvc01/data02
打印结果:
age: 18 money: 20.0
@RequestParam可以设置别名,如果设置了别名,传参时名字必须与别名相同
访问/data03的url:
http://localhost:8080/springmvc01/data03?userAge=60&userMoney=100.0
打印结果:
age: 60 money: 100.0
②、包装类型 && 字符串类型
@RequestMapping("/data04")public void data04(Integer age, Double money) {System.out.println("age: " + age + " money: " + money);}@RequestMapping("/data05")public void data05(String userName, String userPwd) {System.out.println("userName: " + userName + " userPwd: " + userPwd);}
包装类型和字符串类型其它的访问方式和参数赋值的方法和上述基本类型都基本相同,包括@RequestParam等注解的使用,但是不同的一点是,它们都有默认值为null。
访问data04和data04的url:
http://localhost:8080/springmvc01/data04
http://localhost:8080/springmvc01/data05
运行结果:
age: null money: null
userName: null userPwd: null
③、数组类型 && javaBean类型
@RequestMapping("/data06")public void data06(String[] ids) {for (String id : ids) {System.out.println("--- " + id);}}@RequestMapping("/data07")public void data07(User user) {System.out.println(user);}
User类:
public class User {private String id;private String userName;private String userPwd;// 。。。。。getter、setter、toString等方法
}
访问data06:
http://localhost:8080/springmvc01/data06?ids=1&ids=2&ids=3
运行结果:
--- 1
--- 2
--- 3
访问data07:
http://localhost:8080/springmvc01/data07?id=1&userName=admin&userPwd=123456
运行结果:
User{id='1', userName='admin', userPwd='123456'}
传参时候注意与实体类的字段一一对应即可。
④、集合类型
以List类型为例子,Set、Map使用较少,和List使用相似.
重新定义两个类来举例子:
public class Phone {private String num;// getter、setter、toString等方法
}public class User {private String id;private String userName;private String userPwd;List<Integer> ids;List<Phone> phones;// getter、setter、toString等方法
}
webapp目录下添加一个test.jsp文件:
<%--Created by IntelliJ IDEA.User: suxuchaoDate: 2025/4/28Time: 17:06To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>测试集合类型数据</title>
</head>
<body><form action="data08" method="post"><input name="ids[0]" value="123456"><br><input name="ids[1]" value="4567"><br><input name="phones[0].num" value="123321"><br><input name="phones[1].num" value="789987"><br><button type="submit">提交</button></form>
</body>
</html>
测试后台代码:
@RequestMapping("/data08")public void data08(User user) {System.out.println(user);}
访问界面:
打印结果:
User{id='null', userName='null', userPwd='null', ids=[123456, 4567], phones=[phone{num='123321'}, phone{num='789987'}]}
谨记集合类型是封装到java Bean中进行访问的。
5. SpringMVC请求转发与重定向
5.1 请求域对象设置API
@RequestMapping("/model01")public ModelAndView model01() {ModelAndView modelAndView = new ModelAndView();// 设置数据模型(请求域对象)modelAndView.addObject("hello", "Hello Model01");// 设置视图modelAndView.setViewName("hello");return modelAndView;}@RequestMapping("/model02")public String model02(ModelMap modelMap) {modelMap.addAttribute("hello", "Hello Model02");return "hello";}@RequestMapping("/model03")public String model03(Model model) {model.addAttribute("hello", "Hello Model02");return "hello";}@RequestMapping("/model04")public String model04(Map map) {map.put("hello", "Hello Model02");return "hello";}@RequestMapping("/model05")public String model05(HttpServletRequest servletRequest) {servletRequest.setAttribute("hello", "Hello Model02");return "hello";}
访问结果:
最后结果都是成功的,以model02举例。
方法 | 域对象类型 | 作用域 | 数据存储位置 | 使用方式 | 是否需要手动创建 | 数据可见范围 | 典型场景 |
---|---|---|---|---|---|---|---|
model01 | ModelAndView | 请求域(隐式) | 请求域 | 通过 addObject() 添加数据,需显式设置视图 | 是(需手动实例化) | 仅当前请求及转发链有效 | 需同时传递数据和指定视图 |
model02 | ModelMap | 请求域 | 请求域 | 通过 addAttribute() 添加数据,自动绑定 | 否(Spring 自动注入) | 仅当前请求及转发链有效 | 简化数据传递,无需处理视图逻辑 |
model03 | Model | 请求域 | 请求域 | 通过 addAttribute() 添加数据,自动绑定 | 否(Spring 自动注入) | 仅当前请求及转发链有效 | 轻量级数据传递,接口方法更简洁 |
model04 | Map | 请求域 | 请求域 | 直接操作 Map ,Spring 自动转换 | 否(Spring 自动注入) | 仅当前请求及转发链有效 | 快速存取键值对,无需接口约束 |
model05 | HttpServletRequest | 请求域 | 请求域 | 通过 setAttribute() 直接操作 | 是(需注入或获取) | 仅当前请求及转发链有效 | 需直接使用 Servlet API 的场景 |
前四种方式底层还是调用的HttpServletRequest方法,只不过被框架封装了。
5.2 请求重定向
重定向是发一个302状态码给服务器,浏览器自己去请求跳转的页面,地址栏发生改变。
重定向以redirect:开头:
@Controller
public class ViewController {@RequestMapping("/view01")public String view01() {return "redirect:view.jsp";}@RequestMapping("/view02")public String view02() {return "redirect:view.jsp?uName=admin&uPwd=123456";}@RequestMapping("/view03")public String view03() {return "redirect:view.jsp?uName=张三&uPwd=我去";}@RequestMapping("/view04")public String view04(RedirectAttributes attributes) {//设置参数attributes.addAttribute("uName", "张三");attributes.addAttribute("uPwd", "我去");return "redirect:view.jsp";}@RequestMapping("/view05")public ModelAndView view05(ModelAndView modelAndView) {//设置模型数据modelAndView.addObject("uName", "张三");modelAndView.addObject("uPwd", "123456");//设置视图modelAndView.setViewName("redirect:view.jsp");return modelAndView;}@RequestMapping("/view06")public ModelAndView view06(ModelAndView modelAndView) {//设置模型数据modelAndView.addObject("uName", "张三");modelAndView.addObject("uPwd", "123456");//设置视图modelAndView.setViewName("redirect:test");return modelAndView;}
}
- 请求view01:
http://localhost:8080/springmvc01/view01
测试结果:
可以看到,地址栏的url发生了变化,同时也成功跳转到了页面。
- 请求view02:
http://localhost:8080/springmvc01/view02
运行结果:
3. 请求view03:
http://localhost:8080/springmvc01/view03
运行结果(中文传递乱码):
4. 请求view04
http://localhost:8080/springmvc01/view04
运行结果(通过RedirectAttributes传递中文参数):
5. 请求view05
http://localhost:8080/springmvc01/view05
运行结果(通过ModelAndView传递中文参数):
6. 请求view06
http://localhost:8080/springmvc01/view06
最后会映射到:
http://localhost:8080/springmvc01/test路径,这说明不仅可以映射到视图文件,也可以重定向到其它控制器。
补充:
之所以要引入RedirectAttributes和ModelAndView等对象来存储重定向后的数据,是因为重定向之后,新的url无法共享之前旧请求域的数据,因此需要通过这些类来实现数据的传递。
5.3 请求转发
请求转发,直接调用跳转的页面,让它返回。对于浏览器来说,它无法感受到服务器有没有forward,地址栏不发生改变。可以获取请求域中的数据。
添加/text接口
@Controller
public class TestController {@RequestMapping("/test")public void test(String uName, String uPwd) {System.out.println("uName: " + uName + " uPwd: " + uPwd);}
}
测试用例:
@RequestMapping("/view07")public String view07() {return "forward:view.jsp";}@RequestMapping("/view08")public String view08() {return "forward:view.jsp?uName=张三&uPwd=123456";}@RequestMapping("/view09")public String view09(Model model) {model.addAttribute("name", "李四");return "forward:view.jsp?uName=张三&uPwd=123456";}@RequestMapping("/view10")public String view10(Model model) {model.addAttribute("name", "管理员");return "/../../view";}@RequestMapping("/view11")public ModelAndView view11(ModelAndView modelAndView) {modelAndView.setViewName("forward:test");return modelAndView;}
- 访问view07:
http://localhost:8080/springmvc01/view07
运行结果:
2. 访问view08:
http://localhost:8080/springmvc01/view08
运行结果:
- 访问view09:
http://localhost:8080/springmvc01/view09
运行结果:
4. 访问view10:
http://localhost:8080/springmvc01/view10
运行结果:
5. 访问view11:
http://localhost:8080/springmvc01/view11
打印运行结果:
uName: null uPwd: null
6. JSON请求
JSON是企业开发中的通用接口参数类型,在客户端解析很方便。SpringMVC对于json提供了很好的支持。
6.1配置JSON环境
pom.xml:
添加一下依赖:
<!-- 添加 Jackson JSON 依赖 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.10.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.10.0</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.10.0</version></dependency>
修改servlet-context.xml文件:
<!-- 开启注解驱动 --><mvc:annotation-driven><mvc:message-converters><bean class="org.springframework.http.converter.StringHttpMessageConverter" /><bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /></mvc:message-converters></mvc:annotation-driven>
- StringHttpMessageConverter:处理纯文本数据,适合简单 API。
- MappingJackson2HttpMessageConverter:处理 JSON 数据,是 RESTful API 的核心组件。
6.2 @ResponseBody注解
该注解的含义是,表明返回的数据是JSON格式的。
该注解既可以作用于方法上,也可以作用与返回值上:
@Controller
@RequestMapping("/user")
public class userController {@RequestMapping("/query01")@ResponseBodypublic User queryUser01() {User user = new User();user.setId(1);user.setUserName("zhangsan");user.setUserPwd("123456");// 返回User对象return user;}@RequestMapping("/query02")public @ResponseBody User queryUser02() {User user = new User();user.setId(2);user.setUserName("lisi");user.setUserPwd("567765");// 返回User对象return user;}@RequestMapping("/query03")@ResponseBodypublic List<User> queryUser03() {User user = new User();user.setId(1);user.setUserName("zhangsan");user.setUserPwd("123456");User user2 = new User();user.setId(2);user.setUserName("lisi");user.setUserPwd("567765");List<User> list = new ArrayList<>();list.add(user); list.add(user2);// 返回User对象return list;}
}
访问query01:
访问query02:
访问query03:
6.3 @RequestBody注解
@RequestBody注解常用来处理content-type是application/json或者application/xml等格式的内容。一般来说是处理application/json 用的比较多。@RequestBody接受的是一个字符串,一定是一个字符串。
通过@RequestBody可以将请求体中的JSON字符串绑定到相应的bean上,当然也可绑定到对应的字符串上。
@RequestMapping("/query05")@ResponseBodypublic User query05(@RequestBody User user) {System.out.println(user);return user;}
需要注意的是,添加该注解之后,发送请求的时候也需要是JSON格式,否则会报错。
报错:
通过ApiPost进行测试成功结果:
7. 拦截器
SpringMVC中的Intercepter拦截器主要用于拦截用户请求并进行相应的处理,比如通过它来进行权限验证,或判断用户是否登录等操作。实现方式有两种
实现接口: org.springframework.web.servlet.HandlerInterceptor
继承适配器: org.springframework.web.servlet.handler.HandlerInterceptorAdapter
测试接口:
@Controller
public class HelloController {/*** 请求地址的映射** @return*/@RequestMapping("/hello")public ModelAndView hello() {System.out.println("拦截的方法....");ModelAndView modelAndView = new ModelAndView();// 设置数据modelAndView.addObject("hello", "Hello SpringMvc");// 设置视图名称modelAndView.setViewName("hello");return modelAndView;}}
7.1 实现HandlerInterceptor接口
public class MyInterceptor01 implements HandlerInterceptor {/*** 在目标方法(handler)执行前执行的方法* 返回true: 执行handler方法* 返回false: 组织目标handler执行* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("handler方法执行前打印结果....");return true;}/*** 在目标方法(handler)执行后,视图生成前执行的方法* 返回true: 执行handler方法* 返回false: 组织目标handler执行* @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("handler方法执行后,视图生成前打印结果....");}/*** 在目标方法(handler)执行后,视图生成后执行的方法* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("视图生成后打印结果....");}
}
配置映射文件(servlet-context.xml):
<!-- 配置拦截器 --><!-- 1、拦截所有资源 --><mvc:interceptors><!--使用一个bean标签定义一个interceptor直接定义在mvc:interceptors标签中,表示拦截器会拦截所有请求--><bean class="com.xxxx.springmvc.interceptor.MyInterceptor01"/></mvc:interceptors><!-- 2、具体配置路径 --><mvc:interceptors><mvc:interceptor><!-- 通过 mvc:mapping 配置需要被拦截的资源,支持通配符,可配置多个 --><mvc:mapping path="/**"/><!-- 配置不需要拦截的资源,表示/url路径下的所有资源不需要拦截 --><mvc:exclude-mapping path="/url/*"/><bean class="com.xxxx.springmvc.interceptor.MyInterceptor01"/></mvc:interceptor></mvc:interceptors>
上述有两种拦截方式,其中第一种就是全局拦截,后面是可以配置不需要拦截的,还是很方便的。
运行结果:
访问:
http://localhost:8080/springmvc01/hello
打印结果:
handler方法执行前打印结果....
拦截的方法....
handler方法执行后,视图生成前打印结果....
视图生成后打印结果....
7.2 继承HandlerInterceptorAdapter类
HandlerInterceptorAdapter类本质上还是实现了AsyncHandlerInterceptor接口,而AsyncHandlerInterceptor接口继承了HandlerInterceptor接口。
public class MyInterceptor02 extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("handler方法执行前打印结果....");return true;}
}
配置xml文件:
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.xxxx.springmvc.interceptor.MyInterceptor02"/></mvc:interceptor></mvc:interceptors>
打印结果:
handler方法执行前打印结果....
拦截的方法....
7.3 拦截器链
简单来说,就是让多个拦截器都生效,这些拦截器就是拦截器链。
优先级则是先配置的拦截器先执行:
servlet-context.xml配置:
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.xxxx.springmvc.interceptor.MyInterceptor01"/></mvc:interceptor><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.xxxx.springmvc.interceptor.MyInterceptor02"/></mvc:interceptor></mvc:interceptors>
打印运行结果:
MyInterceptor01类的handler方法执行前打印结果....
MyInterceptor02类的handler方法执行前打印结果....
拦截的方法....
MyInterceptor01类的handler方法执行后,视图生成前打印结果....
MyInterceptor01类的视图生成后打印结果....
8. 文件上传
8.1 环境配置
pom.xml:
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.2</version>
</dependency>
servlet-context.xml:
<!-- 文件上传 --><bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 允许文件上传的最大尺寸 --><property name="maxUploadSize"><value>104857600</value></property><!--设置文件进入临时文件夹的最大大小限制该值为阈值,高于此值,保存在内存中,否则生成硬盘上的临时文件--><property name="maxInMemorySize"><value>4096</value></property></bean>
添加前端upload.jsp文件:
<%--Created by IntelliJ IDEA.User: suxuchaoDate: 2025/4/30Time: 15:30To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>文件上传</title>
</head>
<body><form method="post" action="uploadFile" enctype="multipart/form-data"><input type="file" name="file"><button>上传</button></form>
</body>
</html>
8.2 单文件上传
添加result.jsp用于呈现结果:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>result界面</title>
</head>
<body>
<h3>${msg}</h3>
</body>
</html>
文件上传代码实现:
@Controller
public class fileController {/*** 单文件上传* @return*/@RequestMapping("/uploadFile")public String uploadFile(HttpServletRequest request, @RequestParam("file") MultipartFile file) {if(!file.isEmpty()) {// 获取项目所在的路径(绝对路径 )String path = request.getServletContext().getRealPath("/");// 设置上传文件存放的目录File uploadFile = new File(path + "/upload");//判断文件目录是否存在,不存在就先创建if(!uploadFile.exists()) {// 新建目录uploadFile.mkdir();}try {//获取上传文件的文件名String originalFilename = file.getOriginalFilename();//获取文件后缀名String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));// 通过系统当前的毫秒数,生成随机文件名String fileName = System.currentTimeMillis() + suffix;// 上传文件(转存文件到指定目录)file.transferTo(new File(uploadFile.getPath(), fileName));// 如果上传成功,设置作用域request.setAttribute("msg", "文件上传成功!");} catch (IOException e) {request.setAttribute("msg", "文件上传失败");throw new RuntimeException(e);}} else {// 如果上传文件不存在request.setAttribute("msg", "上传文件不存在");}return "result";}
}
前端界面:
运行结果:
8.3 多文件上传
与多文件上传类似,但是需要改动一些小的点。
upload.jsp改动:
<form method="post" action="uploadFiles" enctype="multipart/form-data"><input type="file" name="files"><input type="file" name="files"><button>上传</button></form>
后台代码:
@RequestMapping("/uploadFiles")public String uploadFiles(HttpServletRequest request, @RequestParam("files")List<MultipartFile> files) {if(!files.isEmpty() && files.size() > 0) {for (MultipartFile file : files) {// 上传文件// 获取项目所在的路径(绝对路径 )String path = request.getServletContext().getRealPath("/");// 设置上传文件存放的目录File uploadFile = new File(path + "/upload");//判断文件目录是否存在,不存在就先创建if(!uploadFile.exists()) {// 新建目录uploadFile.mkdir();}try {//获取上传文件的文件名String originalFilename = file.getOriginalFilename();//获取文件后缀名String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));// 通过系统当前的毫秒数,生成随机文件名String fileName = System.currentTimeMillis() + suffix;// 上传文件(转存文件到指定目录)file.transferTo(new File(uploadFile.getPath(), fileName));// 如果上传成功,设置作用域request.setAttribute("msg", "文件上传成功!");} catch (IOException e) {request.setAttribute("msg", "文件上传失败");throw new RuntimeException(e);}}}return "result";}
9. 全局异常
在JavaEE项目开发过程中,会遇到很多各种各样的bug,每个过程单独处理,系统的代码耦合度会太高。因此,SPringleMVC对于异常处理这里提供了支持,通过其提供的全局异常处理机制将异常信息从处理过程抽离出来,既保证了相关处理过程的单一,也实现了异常信息的统一和维护。
- 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver
- 实现Spring的异常处理接口 HandlerExceptionResolver定义自己的异常处理器
- 使用@ExceptionHandler注解
9.1 SimpleMappingExceptionResolver处理
首先在/hello触发器上添加一个简单的错误:
System.out.println(1/0);
访问url:
http://localhost:8080/springmvc01/hello
未配置全局异常处理之前:
配置servlet-context.xml之后:
<!-- 全局异常处理配置 --><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><!-- 页面跳转时出现异常,设置默认错误页面 --><property name="defaultErrorView" value="error"/><!-- 异常发生时,设置异常的变量名 --><property name="exceptionAttribute" value="ex"/></bean>
访问结果:
成功跳转到了错误处理页面 error.jsp。
这个也可以捕获自己的自定义异常,想将不同的异常映射到不同的页面也可以在xml进行配置。
servlet-context.xml 文件配置:
<!-- 设置自定义义异常映射和页面映射 --><property name="exceptionMappings"><props><!-- key代表的是自定义异常常用的路径;括案中设置的是具体的页面 --><prop key="com.xxxx.springmvc.exception.ParamsException">params_error</prop><prop key="com.xxxx.springmvc.exception.BusinessException">business_error</prop></props></property>
9.2 实现接口 HandlerExceptionResolver定义自己的异常处理器
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {ModelAndView mv = new ModelAndView("error");mv.addObject("ex", "默认的错误信息");return mv;}
}
这样实现接口之后就可以生效了。
访问/hello接口触发器,成功跳转:
9.3 @ExceptionHandler注解处理
页面处理程序继承 BaseController
public class BaseController {@ExceptionHandlerpublic String exc(HttpServletRequest request, HttpServletResponse response, Exception ex){request.setAttribute("ex", ex);if(ex instanceof ParamsException){return "error_param";}if(ex instanceof BusinessException){return "error_business";}return "error";}
}
使用 @ExceptionHandler 注解须知异常处理,具有继承特性。有扩展性好(只需要要将要异常处理的 Controller 类继承于 BaseController 即可),不需要附加 Spring 配置等优点,但该方法还已有有个码存储入缓存(需要修改已有个码,使相关类继承于 BaseController),在异常处理时不建议取顶级条件外的数括。
9.4 未捕获异常的处理
对于 Unchecked Exception 而言,由于不强制使用捕获技术,往往在接爱隐藏,如果运行期产生 Unchecked Exception,屏幕中又没有进行相应的捕获和处理,则我们可能看不到页面上的 404,500……等服务端内部错误提示页面。
此需要一个全局的异常处理机制。目前大多数据服务端都支持在 web.xml 中通过(Websphere/Weblogic)或者 (Tomcat)中点配置将定义异常映射显示页面。修改 web.xml 文件,增加以下内容:
<!-- 全局页面定义 -->
<error-page><exception-type>java.lang.Throwable</exception-type><location>/500.jsp</location>
</error-page>
<error-page><error-code>500</error-code><location>/500.jsp</location>
</error-page>
<error-page><error-code>404</error-code><location>/404.jsp</location>
</error-page>