SpringMVC笔记

SpringMVC

是一个Spring的Web框架,基于原生的Serlvet,通过功能强大的DispatcherServlet(继承Servlet),对请求和响应进行统一处理。

Demo

IDEA、Maven3.8.4、Tomcat8、Spring5.3.16

1. 创建maven项目

2. 导入依赖

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

<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.16</version>
</dependency>

<!-- 日志 -->
<dependency>
<groupId>org.atteo.moonshine</groupId>
<artifactId>logback</artifactId>
<version>0.9</version>
</dependency>

<!-- Servlet API -->
<!-- Tomcat自带的jar,所以不用导入, Tomcat还自带了JSP的jar -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 这句要写,不然会依赖冲突-->
<!-- 编译需要所以需要导入,打包不需要,所以需要provided参数 -->
<scope>provided</scope>
</dependency>

<!-- Spring5和thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>
</dependencies>

3. 打包方式修改为war

1
<packaging>war</packaging>

4. 添加web模块

  • src/main下新建webapp文件夹(文件夹名称固定webapp)
  • webapp文件夹下新建/WEB-INF/web.xml文件

5. 配置web.xml

浏览器不能直接访问一个类,要想访问某个类,须得给类以路径。当访问到路径符合类的映射时,servlet就会进行处理。

注册SpringMVC的前端控制器DispatcherServlet

默认配置方式

此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为 -servlet.xml,例如,以
下配置所对应SpringMVC的配置文件位于WEB-INF下,文件名为springMVC-servlet.xml

1
2
3
4
5
6
7
8
9
10
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求进行统一处理-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

扩展的配置方式

可通过init- param标签设置SpringMVC配置文件的位置和名称,通过load-on-startup标签设置SpringMVC前端控
制器DispatcherServlet的初始化时间

通过<init-param>参数指定SpringMVC配置文件的名字和路径

须得在<param-value>的值路径下创建同名的文件。(在这里即在resources下创建SpringMVC.xml文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 配置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>
<!--配置Servlet的初始化时间,优化第一次访问的速度-->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

路径/和路径/*的区别

/:表示当前浏览器发送的所有请求,不包括.jsp为后缀的请求

/*:包括.jsp路径的请求

.jsp由指定的特俗的Servlet进行处理,不需要DispatcherSerlet进行处理。假如由DispatcherServlet进行处理,那么jsp请求将不能访问正确的路径。

6. 创建请求控制器

由于前端请求所有都经过DispatcherServlet,参数获取也在这处理。所以不需要创建Servlet,而只需要创建请求控制器就行。

io.ainexur.mvc.controller.HelloController.java

1
2
3
4
5
import org.springframework.stereotype.Controller;

@Controller //注解标识该类为控制器类
public class HelloController {
}

7. 配置springMVC.xml文件

注意添加context命名空间,不然会访问不到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--扫描组件-->
<context:component-scan base-package="io.ainexur.mvc.controller"></context:component-scan>

<!-- 配置Thymeleaf视图解析器 -->
<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>

8. 配置首页访问

templates下的页面不能直接访问,需要通过servlet

新建/WEB-INF/templates/index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>这是首页</h1>
</body>
</html>

控制器添加首页访问控制

1
2
3
4
5
//请求路径"/" -->> /WEB-INF/template/index.html
@RequestMapping(value = "/") //请求映射,当浏览器访问该路径时,被识图解析器解析
public String index() {
return "index"; //该视图名称会被识图解析器解析(即加上前后缀的路径)
}

tomcat运行项目,访问localhost:8080/context-root/时,通过资源控制器访问请求控制类中的方法,方法返回值则将通过视图解析器补全资源页面的路径,将页面数据返回到客户端。

绝对路径和相对路径

/target和target:前者绝对路径,后者相对路径。

1
2
3
4
<a href="/target">访问目标页面target.html</a>
<a href="target">访问目标页面target.html</a>
<!--Thymeleaf上的对控制器的访问-->
<a th:href="@{/target}">访问目标页面target.html</a>

9. 总结

浏览器访问-> 前端控制器解析url-pattern->DispatcherServlet处理->前端控制器读取SpringMVC的核心配置文件,通过扫面组件找到控制器,将请求地址和控制器中的@RequestMapping注解中的value属性值匹配->控制器方法处理,返回字符串类型的视图名称,该视图会被视图解析器解析,加上前后缀组成路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应的页面

@RequestMapping

@ReuquestMapping 的功能

作用:将请求和处理的控制方法关联,建立映射关系

SpringMVC接受到指定的请求,就会来找到在映射关系中对应的控制方法来处理这个请求。

@RequestMapping注解的位置

类:该类下的所有方法受到该注解约束

方法:该方法受到该注解约束

@ReuquestMapping 的value属性

value属性是必须设置的。

当value值是数组时,表示任意的值都可以匹配

1
2
3
4
//value数组
@RequestMapping(value = {"/hello","/hi"})
//value对应单个值
@RequestMapping(value = "/hello")

@ReuquestMapping 的method属性

method标注了方法的合法请求方式,比如:GET、POST

当Method值不匹配时,将出现405错误

不设置method时,任何method都可以

1
2
3
4
5
6
//多个值
@RequestMapping(
method = {RequestMethod.GET, RequestMethod.POST}
)
//单个值
@RequestMapping( method = RequestMethod.GET )

派生注解:

派生注解默认设置了RequestMapping的method值,其他并无区别

  • GetMapping
  • PostMapping
  • PutMapping
  • DeleteMapping

@ReuquestMapping 的params属性

1
2
3
4
@ReuquestMapping(params = {"username"}) //表示请求中必须包含username属性
@ReuquestMapping(params = {"!username"}) //表示请求中必须不能有username属性
@ReuquestMapping(params = {"username=admin"}) //表示请求中必须包含username属性,且值必须为admin
@ReuquestMapping(params = {"username!=admin"}) //表示请求中必须包含username属性,且值必须不能为admin

@ReuquestMapping 的headers属性

用法和params一样。

1
@ReuquestMapping(headers = {"Host=localhost:8080"}) //表示请求中必须包含Host属性,且值必须为localhost:8080

SpringMVC支持ant风格的路径(模糊匹配)

  • ?:表示任意的单个字符

    有接口如下:

    1
    @RequestMapping(value = "/a?a/testAnt")

    对应url:

    1
    2
    3
    4
    5
    6
    //匹配
    /a1a/testAnt //?可以匹配一个数字
    /aSa/testAnt //?可以匹配一个字母

    //不匹配
    /a?a/testAnt //?不可以匹配,因为是url的特殊符号,其他特俗符号同理
  • *:表示任意的0个或多个字符

    1
    @RequestMapping(value = "/a*a/testAnt")
  • **:表示任意的一层或多层目录

    1
    2
    3
    4
    5
    6
    @RequestMapping(value = "/**/testAnt")	//正确写法

    //错误用法
    @RequestMapping(value = "/a**a/testAnt") //这样相当于两个单独的*的匹配规则

    ///总结:在使用**时,只能使用`/**/xxx`的方式

SpringMVC支持路径中的占位符(重点)

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

SpringMVC路径中的占位符常用于restful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参

1
2
3
4
5
@RequestMapping(value = "/testPath/{id}") //举例url: testPath/999,不加/999这一id层,服务器报404
public String testPath(@PathVariable("id") String id) {
System.out.println("id = " + id);
return "success";
}

支持多占位符

1
2
3
4
5
6
@RequestMapping(value = "/testPath/{id}/{username}") //举例url: testPath/999/zhangsan
public String testPath2(@PathVariable("id") String id, @PathVariable("username")String username) {
System.out.println("id = " + id);
System.out.println("username = " + username);
return "success";
}

占位符{}告诉程序对应url位置的值为参数

@PathVariable("id") String id中的id告诉该形参对应哪个参数

SpringMVC获取请求参数

DispatcherServlet的工作:浏览器发过来的所有请求都被前端控制器先处理,再执行相对应的控制器方法。当DispathcerServlet间接调用控制器方法时,通过路径映射找到要间接调用的方法。这个找的过程时是在DispatcherServlet内部执行,DispatcherServlet中,为我们封装了很多数据,当我们去调用当前的控制器方法时,会根据控制器方法的参数为当前控制器方法注入参数,即为参数赋值

1. 通过Serlvet原生API获取

将HttpServletRequest作为控制器方法形参,由DispatcherServlet注入参数。从HttpServletRequest中获取请求参数。

控制器方法:

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/testServletAPI")
//形参位置的request表示当前请求。
//该控制器方法由DispatcherServlet调用,并自动注入参数
public String testServletAPI(HttpServletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username = " + username);
System.out.println("password = " + password);
return "success";
}

前端Thymeleaf:

1
<a th:href="@{/testServletAPI?username='admin'&password='123'}">测试使用ServletApi获取请求</a>

2. 通过控制器方法的形参获取请求参数

控制器方法中的形参名和url中的请求参数名保持一致,就可以通过DispatcherServlet自动将请求参数赋值到形参,使控制方法获取请求参数。

原理:通过反射获取形参名,再将容器中对应的同名请求参数值为参数,使用反射式的函数调用。

1
2
3
4
5
6
7
8
@RequestMapping("/testParam")
public String testParam(String username, String password, String[] hobby) {
//多个同名参数可以使用String接收,每个值之间用,分割,也可以使用String数组接收。1
System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println("Arrays.toString(hobby) = " + Arrays.toString(hobby));
return "success";
}

3. @RequestParam注解

在形参前添加,使用该注解将名和形参建立映射关系

当前端传的名字为user_name时,DispatcherServlet不会主动将该字段注入形参usernam中。如下:

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(@RequestParam("user_name") String username) { //注解建立映射
System.out.println("username = " + username);
return "success";
}

value:映射的请求参数的名称

requred:默认为true,即当访问时不带value名称的参数报404,带则正常访问。定义为false时,带不带都可以访问。

1
@RequestParam(value = "username", required = true

defaultValue:当不传该参数时,设置默认值。传参数则使用参数值。

1
@RequestParam(value = "username", required = false, defaultValue = "defaultName"

4. RequestHeader注解

将请求头信息和控制器方法的形参创建映射关系。

拥有value、required、defaultValue三个属性,用法和@RequestParam相同

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(@RequestParam("user_name") String username, ) { //注解建立映射
System.out.println("username = " + username);
return "success";
}

5. CookieValue注解

将请求头Cookie和控制器方法的形参创建映射关系。

拥有value、required、defaultValue三个属性,用法和@RequestParam相同

1
2
3
4
5
@RequestMapping("/testParam")
public String testParam(@CookieValue("JSESSIONID") String JSESSIONID) {
System.out.println("JSESSIONID = " + JSESSIONID);
return "success";
}

6. 通过控制器方法的Pojo形参获取请求参数

1
2
3
4
5
@RequestMapping("/testPojo")
public String testPojo(User user) {
System.out.println("user = " + user);
return "success";
}
1
2
3
4
5
6
<form th:action="@{/testPojo}" method="post">
id:<input type="text" name="id"><br>
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="测试Pojo形参获取请求参数">
</form>
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
package io.ainexur.mvc.pojo;

public class User {
private Integer id;
private String username;
private String password;

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

7. 解决获取请求参数乱码问题

服务器执行顺序

监听器-过滤器-Servlet-控制方法

Get请求中文乱码

Tomcat8以下Tomcat服务器的原因,将Tomcat的server.xml的配置加上URLEncoding="UTF-8"

1
2
3
<Connector port="8080" URLEncoding="UTF-8" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

Tomcat8:

  • 找到两个idea的vmoptions配置文件,在文件中追加-Dfile.encoding=UTF-8

  • 或者直接修改idea中的tomcat配置,在vm-options中添加-Dfile.encoding=UTF-8,两者效果相同,都是更改系统默认的GBK编码

post乱码:web.xml配置过滤器及其编码

通过源码追踪,得出要设置编码才会进行编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>forceRequestEncoding</param-name>
<param-value>true</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>

<url-pattern>/*</url-pattern>注意,如果写/不生效,目前还不懂为什么

域对象共享数据

1. 通过ServletAPI向Request域对象共享数据

控制方法

1
2
3
4
5
@RequestMapping("/testRequestByServletApi")
public String testRequestByServletApi(HttpServletRequest request) {
request.setAttribute("testRequestScope", "hello, servletApi");
return "success";
}

访问接口:

1
<a th:href="@{/testRequestByServletApi}">测试testRequestByServletApi</a>

success.html

1
<p th:text="${testRequestScope}"></p>	//成功获取到"hello, servletApi"

2. 通过ModelAndView向Request域对象共享数据

model:指的是往域对象共享数据的过程

view:最终经过视图解析器跳转到最终页面的过程

例子

1
2
3
4
5
6
7
8
9
@RequestMapping("testModelAndView")
public ModelAndView testModelAndView() { //必须是ModelAndView类型的返回数据才能起作用
ModelAndView modelAndView = new ModelAndView();
//处理模型数据:向Request共享数据
modelAndView.addObject("testRequestScope", "Hello, ModelAndView");
//设置视图名称,
modelAndView.setViewName("success");
return modelAndView;
}

3. 通过Model向Request域对象共享数据

例子

1
2
3
4
5
@RequestMapping("testModel")
public String testModel(Model model) {
model.addAttribute("testRequestScope", "Hello, Model");
return "success";
}

4. 通过Map向Request域对象共享数据

例子

1
2
3
4
5
@RequestMapping("testMap")
public String testMap(Map<String, String> mp) {
mp.put("testRequestScope", "Hello, Map");
return "success";
}

5. 通过ModelMap向Request域对象共享数据

例子

1
2
3
4
5
@RequestMapping("testModelMap")
public String testModelMap(ModelMap modelMap) {
modelMap.addAttribute("testRequestScope", "Hello, Map");
return "success";
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!