SpringMVC基础学习

Spring MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助简化开发,Spring Web MVC也是要简化日常Web开发的。

Spring MVC主要是通过前端控制器controller中的注解来完成请求处理的。前端无论是以何种方式请求,都会通过controller进行轻度处理、转发以及调度后端的处理器进行处理,最后返回正确的视图及响应。以此来看,springMVC既可以返回合适的页面,也可以响应RESTful请求。

Spring MVC简介

Spring MVC的特征

  1. 让我们能非常简单的设计出干净和整洁的Web层;

  2. 进行更简洁的Web层的开发;

  3. 天生与Spring框架集成(如IoC容器、AOP等);

  4. 提供强大的约定大于配置的契约式编程支持;

  5. 能简单的进行Web层的单元测试;

  6. 支持灵活的URL到页面控制器的映射;

  7. 非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);

  8. 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;

  9. 提供一套强大的JSP标签库,简化JSP开发;

  10. 支持灵活的本地化、主题等解析;

  11. 更加简单的异常处理;

  12. 对静态资源的支持;

  13. 支持Restful风格。

Spring MVC的优势

  1. 清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器(Controller)、验证器(Validator)、命令对象(Command 请求参数绑定到的对象就叫命令对象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。

  2. 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;

  3. 由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象;

  4. 和Spring 其他框架无缝集成,是其它Web框架所不具备的;

  5. 可适配,通过HandlerAdapter可以支持任意的类作为处理器;

  6. 可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;

  7. 功能强大的数据验证、格式化、绑定机制;

  8. 利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;

  9. 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。

  10. 强大的JSP标签库,使JSP编写更容易。

SpringMVC实现HelloWorld

导入SpringMVC依赖

配置web.xml注册DispatcherServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class> <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param> <!--启动级别-1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)--> <!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

编写SpringMVC的配置文件:springmvc-servlet.xml,添加处理映射器,处理器适配器,视图解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--处理映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--Handler-->
<bean id="/hello" class="com.kuang.controller.HelloController"/>
</beans>

编写我们要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.heavytiger.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//注意:这里我们先导入Controller接口
public class HelloController implements Controller {

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,放在ModelAndView中。Model
mv.addObject("msg","HelloSpringMVC!");
//封装要跳转的视图,放在ModelAndView中
mv.setViewName("hello"); //: /WEB-INF/jsp/hello.jsp
return mv;
}

}

编写要跳转的jsp页面,显示ModelandView存放的数据,以及正常页面:

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello world!</title>
</head>
<body>
${msg}
</body>
</html>

可能遇到的问题:访问出现404,排查步骤:

  1. 查看控制台输出,看一下是不是缺少了什么jar包。
  2. 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!
  3. 重启Tomcat 即可解决!

SpringMVC的执行原理

SpringMVC的执行原理

(1) Http请求:客户端请求提交到DispatcherServlet。
(2) 寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller。
(3) 调用处理器:DispatcherServlet将请求提交到Controller。
(4)(5)调用业务处理和返回结果:Controller调用业务逻辑处理后,返回ModelAndView。
(6)(7)处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。
(8) Http响应:视图负责将结果显示到客户端。

使用注解开发,实现HelloWorld

导入SpringMVC依赖

配置web.xml注册DispatcherServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class> <!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param> <!--启动级别-1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)--> <!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

编写SpringMVC的配置文件:springmvc-servlet.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
30
31
32
33
34
35
36
37
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="com.heavytiger.controller"/>
<!-- 让Spring MVC不处理静态资源 .css .js等资源被过滤 -->
<mvc:default-servlet-handler/>
<!--
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven/>

<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
</bean>

</beans>

使用注解编写HelloController:

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
package com.heavytiger.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/hello")
@Controller
public class HelloController {

@RequestMapping("/springMVC")
public String helloSpringMVC(Model model) {
model.addAttribute("msg", "Hello world, this is SpringMVC!");
return "hello"; // return 的结果会被视图解析器解析,拼接后找到视图
}

@RequestMapping("/spring")
public String helloSpring(Model model) {
model.addAttribute("msg", "Hello world, this is Spring!");
return "hello"; // return 的结果会被视图解析器解析,拼接后找到视图
}

@RequestMapping("/mybatis")
public String helloMyBatis(Model model) {
model.addAttribute("msg", "Hello world, this is MyBatis!");
return "hello"; // return 的结果会被视图解析器解析,拼接后找到视图
}
}

结果:

image-20211216223424524

此时访问不同的网页,导入到了不同的页面中

RESTful API风格

REST:英文representational state transfer直译为表现层状态转移,或者表述性状态转移;Rest是web服务的一种架构风格,一种设计风格,是一种思想;同时Rest不是针对某一种编程语言的。

协议

API与用户的通信协议,总是使用HTTPs协议。

域名

应该尽量将API部署在专用域名之下。

应该尽量将API部署在专用域名之下。

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/

版本(Versioning)

应该将API的版本号放入URL。

https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

路径(Endpoint)

路径又称”终点”(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/employees

HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

1
2
3
{
error: "Invalid API key"
}

返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档

Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

1
2
3
4
5
6
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

1
2
3
4
5
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

1
2
3
4
5
6
7
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
"emails_url": "https://api.github.com/user/emails",
"emojis_url": "https://api.github.com/emojis",
// ...
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

重定向及转发

通过SpringMVC来实现转发和重定向 - 无需视图解析器;

测试前,需要将视图解析器注释掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class ResultSpringMVC {
@RequestMapping("/rsm/t1")
public String test1(){
//转发
return "/WEB-INF/jsp/index.jsp";
}

@RequestMapping("/rsm/t2")
public String test2(){
//转发二,手动补上forward: 但是没必要
return "forward:/WEB-INF/jsp/index.jsp";
}

@RequestMapping("/rsm/t3")
public String test3(){
//重定向
return "redirect:/WEB-INF/jsp/index.jsp";
}
}

通过SpringMVC来实现转发和重定向 - 有视图解析器;

重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.

可以重定向到另外一个请求实现 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class ResultSpringMVC2 {
@RequestMapping("/rsm2/t1")
public String test1(){
//转发
return "test";
}

@RequestMapping("/rsm2/t2")
public String test2(){
//重定向
return "redirect:/index";
//return "redirect:hello.do"; //hello.do为另一个请求/
}

}

数据处理

处理提交的数据

提交的域名称和处理方法的参数名一致

提交数据 : http://localhost:8080/hello?name=helloworld

处理方法 :

1
2
3
4
5
@RequestMapping("/hello")
public String hello(String name){
System.out.println(name);
return "hello";
}

后台输出 : helloworld

提交的域名称和处理方法的参数名不一致

提交数据 : http://localhost:8080/hello?username=helloworld

处理方法 :

1
2
3
4
5
6
//@RequestParam("username") : username提交的域的名称 .
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name){
System.out.println(name);
return "hello";
}

后台输出 : helloworld

提交的是一个对象

要求提交的表单域和对象的属性名一致,参数使用对象即可

1、实体类

1
2
3
4
5
6
7
8
public class User {
private int id;
private String name;
private int age;
//构造
//get/set
//tostring()
}

2、提交数据 : http://localhost:8080/user?name=helloworld&id=1&age=18

3、处理方法 :

1
2
3
4
5
@RequestMapping("/user")
public String user(User user){
System.out.println(user);
return "hello";
}

后台输出 : User { id=1, name=’helloworld’, age=18 }

说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则得到的值就是null,因为无法反射注入。

返回JSON数据

解决乱码问题

增加Spring提供的filter,将请求的编码全部修改为utf-8

1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>

返回json字符串统一解决

在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class UserController {

//produces:指定响应体返回类型和编码
@RequestMapping(value = "/json1")
public String json1() throws JsonProcessingException {
//创建一个jackson的对象映射器,用来解析数据
ObjectMapper mapper = new ObjectMapper();
//创建一个对象
User user = new User("hello", 3, "男");
//将我们的对象解析成为json格式
String str = mapper.writeValueAsString(user);
//由于@ResponseBody注解,这里会将str转成json格式返回;十分方便
return str;
}
}

参考资料

[1] 使用Spring MVC - 廖雪峰的官方网站 (liaoxuefeng.com)

[2] 【狂神说Java】SpringMVC最新教程IDEA版通俗易懂_哔哩哔哩_bilibili

[3] Spring MVC【入门】就这一篇! - 简书 (jianshu.com)

[4] RESTful API 设计指南 - 阮一峰的网络日志 (ruanyifeng.com)


-------------本文到此结束 感谢您的阅读-------------
谢谢你请我喝肥宅快乐水(๑>ڡ<) ☆☆☆