SpringMvc

SpringMvc是Spring的一个后续产品,是Spring的一个子项目

SpringMvc是Spring为表述层开发提供的一整套完备的解决方案

创建SpringMvc项目

  1. 创建maven下的web项目

    参考:https://blog.csdn.net/weixin_42222334/article/details/80362126

  2. 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.15</version>
    </dependency>

    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.10</version>
    </dependency>
  3. 配置web.xml文件

    1
    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
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >

    <web-app>
    <!-- 配置SpringMvc的前端控制器,对浏览器发送的请求进行统一处理 -->
    <servlet>
    <servlet-name>SpringMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置SpringMvc配置文件的位置和名称 -->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springMVC.xml</param-value>
    </init-param>
    <!-- 将前端控制器DispatherServlet的初始化时间提前到服务器启动时 -->
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>SpringMvc</servlet-name>
    <!--
    设置springmvc的核心控制器所能处理的请求的请求路径
    /所匹配的请求可以是/login或.js...
    但不能匹配.jsp的请求
    -->
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    </web-app>



配置springMVC.xml

1
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
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!-- 扫描组件 -->
<context:component-scan base-package="com"/>
<!-- 配置视图解析器,也可以选择其他的视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<!-- 视图解析器优先级 -->
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>

扩展:过滤器、拦截器、aop 顺序

由于SpringMVC的前端控制器是DispatherServlet所以有必要了解一下过滤器、拦截器、aop 的执行顺序

filter—>Interceptor—->@Aspect –>Interceptor

img




SpringMVC的请求


@RequestMapping

@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系

  • 标识一个类:设置请求的请求路径的初始信息
  • 标识一个方法:设置映射请求请求路径的具体信息
1
2
3
4
5
6
7
8
9
10
@Controller
@RequestMapping(value="/test")
public class controller {
@RequestMapping(value="index")
public String index(){
//返回视图名称
return "index";
}
}
//http://localhost:8080/springmvc/test/index

浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。前端控制器会读取SpringMvc的核心配置文件,通过扫描组件会找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,通过Thymeleaf(也可以使用其他的视图解析器)对视图进行渲染,最终转发到视图所对应页面


@RequestMapping中的value属性

@RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求

@RequestMapping注解的value属性必须设置

1
2
3
4
5
@RequestMapping(value={"index","test"})
public String index(){
//返回视图名称
return "index";
}

@RequestMapping中的method属性

@RequestMapping中的method属性通过请求的请求方式(get/post匹配请求映射)

@RequestMapping中的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求

1
2
3
4
5
@RequestMapping(value="index",method=RequestMethod.GET)
public String index(){
//返回视图名称
return "index";
}

@RequestMapping的派生注解

  • @GetMapping

    1
    //等同于@RequestMapping(method=RequestMethod.GET)
  • @PostMapping

    1
    //等同于@RequestMapping(method=RequestMethod.POST)


@RequestMapping中的params属性

@RequestMapping中的params属性通过请求的请求参数匹配请求映射

@RequestMapping中的params属性是一个字符串的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系

1
2
3
4
"param":要求请求映射所匹配的请求必须携带param请求参数
"param":要求请求映射所匹配的请求必须不能携带param请求参数
"param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value"
param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value
1
2
3
4
5
@RequestMapping(value="index",param={"usename","!abc"})
public String index(){
//返回视图名称
return "index";
}

SpringMVC支持ant风格的路径

?:表示任意的单个字符

*:表示任意的0个或多个字符

**:表示任意的一层或多层目录




SpringMVC获取请求参数

可以使用servletAPI获取参数,但基本不使用

当发送的参数名与@RequestMapping标识方法中参数名一致时,会自动匹配

1
2
3
4
@RequestMapping(value="index")
public String index(String username, String password){
return "index";
}

当有多个请求参数名一样时,如: …?hobby=a&hobby=b

1
2
3
4
5
6
7
8
9
10
//可以这么写
@RequestMapping(value="index")
public String index(String hobby){ //hobby="a,b"
return "index";
}
//或
@RequestMapping(value="index")
public String index(String[] hobby){ //hobby=[a,b]
return "index";
}

@RequestParam()

1
2
3
4
5
6
7
8
9
10
11
//当请求参数名和方法参数名不一致时,使用@RequestParam()
@RequestMapping(value="index")
public String index(
@RequestParam("user_name") String username,
String password){
return "index";
}

@RequestParam()将请求参数和方法参数映射起来,属性:
required 是否必须带有这个请求参数
defaultValue 不管required属性值为truefalse,当value所指定的请求参数没有传输或传输的值为“”时,则使用默认值

@RequestHeader

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value="index")
public String index(
String username,
String password,
@RequestHeader("Host") String host){
return "index";
}

@RequestHeader()会从请求头中取出对应的字段赋值给参数,属性:
required 是否必须带有这个请求参数
defaultValue 不管required属性值为truefalse,当value所指定的请求参数没有传输或传输的值为“”时,则使用默认值

@CookieValue

与前面的使用方法一样


通过POJO获取请求参数

可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值

1
2
3
4
<form th:action="@{/test}" method="post">
<input type="text" name="username"/>
<input type="password" name="password"/>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
public class User{
public String username;
public String password;
...
}


@RequestMapping("index")
public String index(User user){
System.out.println(user.getUsername() + user.getPassword);
return "index";
}

解决中文乱码问题

在web.xml文件中添加字符编码过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 字符编码过滤器 -->
<filter>
<filter-name>CharacterEncodingFilter</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>
<!-- 设置响应编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 设置字符编码过滤器的过滤路径 -->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>



域对象共享数据

使用servletAPI向request域对象共享数据

可以使用servletAPI向域对象中存放数据,但基本不使用

1
2
3
4
5
@RequestMapping("index")
public String index(HttpServletRequest request){
request.setAttribute("test","testString");
return "index";
}

使用ModelAndView向request域对象共享数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("index")
public ModelAndView index(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域中共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mv = new ModelAndView();
//向请求域中共享数据
mv.addObject("hello","world");
//设置视图,实现页面跳转
mv.setViewName("index");
return mv;
}

使用Model向request域对象共享数据

1
2
3
4
5
@RequestMapping("index")
public String index(Model m){
m.addAttribute("hello","world");
return "index";
}

使用Map向request域对象共享数据

1
2
3
4
5
@RequestMapping("index")
public String index(Map<String,Object> map){
map.put("hello","world");
return "index";
}

使用ModelMap向request域对象共享数据

1
2
3
4
5
@RequestMapping("index")
public String index(ModelMap mm){
mm.addAttribute("hello","world");
return "index";
}

向session域中共享数据

1
2
3
4
5
@RequestMapping("index")
public String index(HttpSession session){
session.setAttribute("hello","world");
return "index";
}

向application域共享数据

1
2
3
4
5
6
@RequestMapping("index")
public String index(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("hello","world");
return "index";
}



SpringMVC的视图


自定义视图解析器

当控制器方法中设置的视图名没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转


转发视图

SpringMVC中默认的转发视图是InternalResourceView

创建转发视图的情况:

当控制器方法中所设置的视图名称以 “forward:” 为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀 “forward:” 去掉,剩余部分作为最终路径通过转发的方式实现跳转(只能跳转@RequestMapping映射过的视图)

例如 “forward:/“,”forward:/employee”

1
2
3
4
@RequestMapping("index")
public String index(){
return "forward:/index";
}

重定向视图

SpringMVC中默认的重定向视图是RedirectView

当控制器方法中所设置的视图名称以 “redirect:” 为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀 “redirect:” 去掉,剩余部分作为最终路径通过重定向的方式实现跳转(只能跳转@RequestMapping映射过的视图)

例如 “redirect:/“,”redirect:/employee”

1
2
3
4
@RequestMapping("index")
public String index(){
return "redirect:/index";
}

视图控制器 view-controller

当控制器方法中,仅仅用来实现页面跳转(即不做任何其他的操作,只是页面跳转),即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示

使用场景:

spring文档里建议并将其做为最佳实践将所有的jsp页面放到WEB-INF下,不让直接访问,那么我们只能通过controller来访问jsp页面了,但是我们总不能为每个页面都写一个controller吧,那样太麻烦了,所以mvc:view-controller就是处理这个场景的一个shortcut(快捷方式)。

image-20220313105546745


配置view-controller后所有映射都会失效

原因:如果没有mvc:annotation-driven,那么所有的@Controller注解可能就没有解析,所有当有请求时候都没有匹配的处理请求类,就都去mvc:default-servlet-handler即default servlet处理了。


<mvc:annotation-driven />的作用

image-20220313105823765


InternalResourceViewResolver控制器

springMCV.xml

1
2
3
4
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>



RESTFul简介

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:”我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。

参考:菜鸟教程


RESTFul的实现

具体来说就是HTTP协议里的四个表示操作方式的动词:GET、POST、PUT、DELETE

  • GET:用于获取资源
  • POST:用于新建资源
  • PUT:用于更新资源
  • DELETE:用于删除资源

REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性

操作 传统方式 REST风格
查询 getUserById?id=1 user/1——>GET
保存 saveUser user——>POST
删除 deleteUser?id=1 user/1——>DELETE
更新 updateUser user——>PUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RequestMapping(value="/user/{id}", method=RequestMethod.GET)
public String getUserById(){
...
}

@RequestMapping(value="/user", method=RequestMethod.POST)
public String saveUser(String username,String password){
...
}

@RequestMapping(value="/user/{id}", method=RequestMethod.DELETE)
public String deleteUser(){
...
}

@RequestMapping(value="/user", method=RequestMethod.PUT)
public String updateUser(String username,String password){
...
}

PUT、DELETE请求方式存在浏览器兼容问题

浏览器确实支持PUT和DELETE,但是HTML不支持。

这是因为HTML 4.01和最终的W3C HTML 5.0规范都说它们的form元素应允许的唯一HTTP方法是GET和POST。

在HTML 5的开发过程中对此进行了很多讨论,有一次他们将它们添加到HTML 5中,然后再次删除。之所以从HTML5规范中删除其他方法,是因为HTML 4级浏览器永远无法支持它们(在制作时不属于HTML)。


解决:在web.xml文件中配置HiddenHttpMethodFilter过滤器

注:同时编码过滤器必须放在HiddenHttpMethodFilter过滤器之前

1
2
3
4
5
6
7
8
9
<!-- 配置HiddenHttpMethodFilter过滤器 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

  • PUT请求表单提交代码

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 显式请求方式为post请求方式 -->
    <form th:action="@{/user}" method="post">
    <!-- 添加一个隐藏域用于更改请求方式 -->
    <input type="hidden" name="_method" value="PUT">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="submit" value="更改">
    </form>

  • 由于DELETE请求通常使用超链接、按钮形式,所以使用js代码将超链接、按钮与隐藏域绑定

    更改DELETE请求参考:如何将超链接GET请求变成DELETE请求



扩展问题:DispatcherServlet是访问不到静态资源的,当DispatcherServlet访问不到时,应该让Tomcat配置的DefaultServlet去访问静态资源,因此需要配置mvc:default-servlet-handler标签,由于配置mvc:default-servlet-handler标签后也会导致映射路径失效,所以也需要配置mvc:annotation-driven标签

如果配置了DispatcherServlet,那么所有的请求都会被拦截,包括静态资源。

  • ‘/‘ 将会替换掉容器的default servlet, 将会处理所有其他handler(Servlet)都不处理的访问请求.
  • 如果web.xml没有配置其他特殊路径的servlet, 基本上所有的请求都交由DispatcherServlet处理.
  • 将不会再访问容器中原始默认的servlet(你对静态资源的访问就是通过容器默认servlet处理的),故而静态资源将不可访问!

1
2
3
4
<!-- 开放静态资源访问 -->
<mvc:default-servlet-handler/>

<mvc:annotation-driven/>


刨根问底一下 为什么加入两个注解就没有问题了呢?

首先是两个注解都不加,此时HandlerMappings中的AnnotationHandlerMapping存储这Controller和url的映射关系,由于我们没有编写Controller去处理js html等静态资源,所以此时的状态是动态资源可以访问,静态资源不可访问。

img

其次是只加上default-servlet-handler,发现处理Controller的AnnotationHandler不见了,取而代之的是SimpleURLHandlerMapping,该Handler种的handlerMap非常简单只有一个/** 即无论什么请求都直接去当前webapp下去找。这样配置静态资源肯定是可以访问的,因为它的作用和不使用SpringMVC中的DIsplacedServlet直接使用Tomcat一样。但由于AnnotationHandler的缺失,导致Controller这种基于注解配置处理请求的方法无法访问,所以这种配置下的状态是静态资源可以访问,动态资源不可以访问。

img

最后当把两个注解都加上的时候,不仅有处理静态资源的SimpleUrlHandlerMapping,还多了一个优先级最高的RequestMapping,点开详情信息发现我们配置的Controller都在里面。这就是我们要的效果:

对于每一个非jsp请求都会被DispatchServlet拦下,然后交给优先级最高的RequestMapping处理。RequestMapping遍历自己的Mappings,如果这个请求是一个动态请求,那么一定可以找到对应的Controller,Controller处理并返回;如果该请求是一个针对静态资源文件的,RequestMapping无能为力,他会按照优先级交给后续HandlerMapping如没啥用的BeanNameUrlHandlerMapping,以及放在最后用来兜底的SimpleUrlHandlerMapping,当SimpleUrlHandlerMapping拿到一个针对静态资源的请求后,会在/**目录下找到静态资源并返回。

img

转载自原创博主



HttpMessageConverter

HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文

HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity


@RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值

1
2
3
4
5
@RequestMapping("/test")
public String deleteUser(@RequestBody String requestBody){
System.out.println(requestBody);
...
}
1
2
3
4
5
<form th:action="@{/user/test}" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>

结果:

1
username="..."&password="..."

RequestEntity

RequestEntity封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可通过getHeaders()获得请求头信息,通过getBody()获取请求体信息

1
2
3
4
5
6
@RequestMapping("/test")
public String deleteUser(RequestEntity<String> requestEntity){
System.out.println(requestEntity.getHeaders());
System.out.println(requestEntity.getBody());
...
}

@ResponseBody

@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器

1
2
3
4
5
@RequestMapping("/test")
@ResponseBody
public String deleteUser(){
return "成功!";
}

结果:浏览器页面显式 成功!


处理 json

1、导入依赖

image-20220322141832294

2、在SpringMVC核心配置文件中开启mvc的注解驱动

1
<mvc:annotation-driven/>

3、在处理器方法上使用@ResponseBody注解进行标识

4、将Java对象直接作为控制器方法的返回值返回时,就会自动转换为json格式字符串


ResponseEntity

ResponseEntity用于控制器方法的返回值类型,该控制器的返回值就是响应到浏览器的响应报文


@RestController注解

@RestController注解是SpringMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody




文件上传和下载


下载

利用ResponseEntity实现下载功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RequestMapping("download")
public ResponseEntity<byte[]> test(HttpSession session) throws IOException {
//获取servletContext对象
ServletContext servletcontext = session.getServletContext ();
//获取服务器中文件的真实路径
String realpath = servletcontext. getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realpath);
//创建字节数组(这里最好使用循环读取)
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String,String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("content-Disposition","attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statuscode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,statuscode) ;
//关团输入流is.close(;
return responseEntity;
}

上传

添加依赖:

1
2
3
4
5
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</ artifactId>
<version>1.3.1</version>
</dependency>

html:

1
2
3
4
5
<!-- 必须使用post请求方式 -->
<form th:action="@{/up}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br>
<input type="submit" value=”上传>
</form>

在SpringMVC核心配置文件中配置文件上传解析器

1
2
3
<!-- 配置文件上传解析器,将上传的文件封装为MultipartFile -->
<!-- 根据id获取的解析器 解析器id必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("up")
public String test(MultipartFile photo,HttpSession session) throws IOException{
String fileName = photo.getOriginalFilename();
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if (!file.exists()){
file.mkdir();
}
String filePath = photoPath + File.separator/*文件分隔符*/ + fileName;
photo.transferTo(new File(filePath));
return "success";
}

最终上传到这个文件夹:image-20220324105512280




拦截器

SpringMVC中的拦截器用于拦截控制器方法的执行

SpringMVC中的拦截器需要实现HandlerInterceptor或者继承HandlerInterceptorAdapter类

SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置


拦截器的配置

1
2
3
4
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="com.interceptors.Interceptor"/> <!-- 对所有请求进行拦截 -->
</mvc:interceptors>

实现拦截器

拦截器三个方法执行时机

  • 控制器方法执行之前
  • 控制器方法执行之后
  • 渲染视图完毕之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Interceptor implements HandlerInterceptor {
@Override
//控制器方法执行之前
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true
/*
true:放行,即调用控制器方法
false:不放行,即不调用控制器方法
*/;
}

@Override
//控制器方法执行之后
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
//渲染视图完毕之后
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

关于preHandle放行可以在DispatcherServlet源码中看见如下代码

image-20220324111906179

进入applyPreHandle方法中可以看见若拦截器的preHandle方法返回true则这个方法返回true,结合DispatcherServlet的源码可知 if 代码块中的return不会执行,可以继续往下走

image-20220324112151787


拦截指定路径

1
2
3
4
5
6
7
8
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 配置需要拦截的路径 -->
<mvc:exclude-mapping path="/"/> <!-- 配置需要排除在外的拦截路径 -->
<bean class="com.interceptors.Interceptor"/> <!-- 配置使用哪个拦截器 -->
</mvc:interceptor>
</mvc:interceptors>

多个拦截器的执行顺序

若每个拦截器的preHandle()都返回true

  • 则此时拦截器的执行顺序与拦截器在SpringMVC配置文件中的配置顺序有关
  • preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行()

image-20220324124618700

若某个拦截器的preHandle()返回了false

  • preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行



异常处理器

SpringMVC提供了一个处理控制器方法执行过程中出现的异常的接口:HandlerExceptionResolver

HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver


使用自定义异常理器

SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver

基于配置的异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 配置异常处理 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
props的键表示处理方法执行过程中出现的异常
props的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="异常全类名">新的视图名称</prop>
</props>
</property>
<!-- 将异常信息以键值对的形式放入请求域中(可选) -->
<property name="exceptionAttribute" value="键名"/>
</bean>

基于注解的异常处理

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice
public class ExceptionController {
//@ExceptionHandler用于设置需要处理的异常
@ExceptionHandler(ArithmeticException.class);
//ex表示当前请求处理中出现的异常对象
public ModelAndView testException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("Exception",ex);
...
}
}




注解配置SpringMVC

使用配置类和注解代替web.xml和SpringMVC配置文件的功能


创建初始化类,代替web.xml

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。

Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的
WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletinitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletlnitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

1
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
//web工程的初始化类,用于代替web.xml
public class webInit extends AbstractAnnotationConfigDispatcherServletInitializer {

/**
* 指定spring的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}

/**
* 指定SpringMVC的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

/**
* 指定DispatcherServlet的映射规则
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

/**
* 注册过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("utf-8");
characterEncodingFilter.setForceResponseEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();

return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
}

getRootConfigClasses、getServletConfigClasses、getServletMappings是必须的以外,还可以重写其他的方法

image-20220324140948565


完成后项目结构如下图

image-20220324141036114


SpringConfig类

1
2
3
4
5
6
7
8
//标志为配置类
@Configuration
//开启扫描组件
@ComponentScan
//开启注解驱动
@EnableWebMvc
public class SpringConfig {
}

Webconfig类

总体:

1
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
//标识当前类为一个配置类
@Configuration
//扫描组件
@ComponentScan("com")
//mvc注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver(){
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext()
);
templateResolver.setPrefix("/WEB-INF/templates");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver){
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}

//生成视图解析器并为解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}

//配置文件解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}

//配置异常处理解析器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
//以键值对形式放入要处理的异常
Properties prop = new Properties();
prop.setProperty("java.lang.Exception","exception");
exceptionResolver.setExceptionMappings(prop);
//将异常信息放入请求域
exceptionResolver.setExceptionAttribute("exception");
resolvers.add(exceptionResolver);
}

//default-servlet-handler
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
Interceptor interceptor = new Interceptor();
//排除拦截路径
registry.addInterceptor(interceptor).excludePathPatterns("/**");
}

//添加view-controller
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}

}

配置视图解析器

1
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
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver(){
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext()
);
templateResolver.setPrefix("/WEB-INF/templates");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}

//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver){
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}

//生成视图解析器并为解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}

配置文件上传解析器

1
2
3
4
5
6
//配置文件解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}

配置异常处理解析器

1
2
3
4
5
6
7
8
9
10
11
12
//配置异常处理解析器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
//以键值对形式放入要处理的异常
Properties prop = new Properties();
prop.setProperty("java.lang.Exception","exception");
exceptionResolver.setExceptionMappings(prop);
//将异常信息放入请求域
exceptionResolver.setExceptionAttribute("exception");
resolvers.add(exceptionResolver);
}

配置default-servlet-handler

1
2
3
4
5
//default-servlet-handler
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

配置拦截器

1
2
3
4
5
6
7
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
Interceptor interceptor = new Interceptor();
//排除拦截路径
registry.addInterceptor(interceptor).excludePathPatterns("/**");
}

配置view-controller

1
2
3
4
5
//添加view-controller
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}