内容输出来源:拉勾教育Java高薪训练营
Spring MVC 是 Spring 给我们提供的⼀个⽤于简化 Web 开发的框架。
第一章 Spring MVC应用
1. Spring MVC简介
1.1. MVC体系结构
1.1.1 经典三层架构(代码架构)
我们的开发架构⼀般都是基于两种形式,⼀种是C/S架构
,也就是客户端/服务器;另⼀种是B/S架构
,也就是浏览器/服务器。在JavaEE
开发中,⼏乎全都是基于B/S架构
的开发。那么在B/S架构
中,系统标准的三层架构包括:表现层、业务层、持久层。
- 表现层
也就是
web
层。负责接收客户端请求,向客户端响应结果。
表现层分为展示层(view
)和控制层(controller
):控制层负责接收请求,展示层负责结果的展示。
表现层依赖于业务层。 -
业务层
也就是我们常说的
service
层。负责业务逻辑处理。
表现层依赖于业务层,但业务层不依赖于表现层。
业务层可能会依赖于持久层。 -
持久层
也就是我们是常说的dao层。
负责数据持久化,包括数据层即数据库和数据访问层。
通俗的讲,持久层就是和数据库交互,对数据库表进⾏增删改查的。
1.1.2 MVC设计模式(代码的组织方式/形式)
MVC全名是Model View Controller
,是模型(model)-视图(view)-控制器(controller)
的缩写,是⼀ 种⽤于设计创建Web应⽤程序表现层的模式。
Mode(模型)
模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业务。
-
View(视图)
通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据 模型数据创建的。
-
Controller(控制器)
是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。
MVC提倡:每⼀层只编写⾃⼰的东⻄,不编写任何其他的代码;分层是为了解耦,解耦是为了维护⽅便和分⼯协作。
1.2. Spring MVC是什么
SpringMVC
全名叫 Spring Web MVC
,是⼀种基于Java
的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork
的后续产品。
Spring MVC
和Struts2
都是一个表现层框架,他们都是基于MVC设计模式的,主要负责处理前端的HTTP请求。
Spring MVC
的本质是对Servlet
的封装。
Spring MVC的作用:
-
- 接收请求
-
- 返回响应,跳转页面
2. Spring MVC ⼯作流程
需求:前端浏览器请求url:http://localhost:8080/demo/handle01 ,前端⻚⾯显示后台服务器的时间
-
- 配置
DispatcherServlet
前端控制器
- 配置
<!--第一步:配置前端控制器DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--第四步:将xml文件地址告诉Spring MVC(DispatcherServlet)-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
方式一:带后缀,比如*.action, *.do, *.aaa
方式二:/ 不包括.jsp
方式三:/* 拦截所有,包括.jsp
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
-
- 开发处理具体业务逻辑的
Handle
(@Controller
、@RequestMapping
)
- 开发处理具体业务逻辑的
/**
* 第二步:开发处理具体业务逻辑的`Handle`
*/
@Controller
@RequestMapping("/demo")
public class DemoController {
/**
* url: http:localhost:8080/demo/handle01
*/
@RequestMapping("/handle01")
public ModelAndView handle01() {
Date date = new Date(); //服务器时间
System.out.println(date);
//返回服务器时间到前端页面
ModelAndView modelAndView = new ModelAndView();
// addObject 其实是向请求域总设置参数 request.setAttribute("date",date)
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
}
-
- xml文件配置
controller
扫描,配置spring web mvc三大件(视图解析器、处理器映射器、处理器适配器)
- 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: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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--第三步:xml文件配置`controller`扫描,配置**spring web mvc**三大件(视图解析器、处理器映射器、处理器适配器)-->
<!--开启controller扫描-->
<context:component-scan base-package="com.lzx.controller"/>
<!--配置试图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--自动注册最合适的处理器映射器,处理器适配器-->
<mvc:annotation-driven/>
</beans>
-
- 将xml文件地址告诉Spring MVC(
DispatcherServlet
)
- 将xml文件地址告诉Spring MVC(
<!--第四步:将xml文件地址告诉Spring MVC(DispatcherServlet)-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
2.1. Spring MVC请求处理流程
流程说明:
- 第⼀步:⽤户发送请求⾄前端控制器
DispatcherServlet
- 第⼆步:
DispatcherServlet
收到请求调⽤HandlerMapping
处理器映射器 - 第三步:处理器映射器根据请求
Url
找到具体的Handler
(后端控制器),⽣成处理器对象及处理器拦截器(如果有则⽣成)⼀并返回DispatcherServlet
- 第四步:
DispatcherServlet
调⽤HandlerAdapter
处理器适配器去调⽤Handler
- 第五步:处理器适配器执⾏
Handler
- 第六步:
Handler
执⾏完成给处理器适配器返回ModelAndView
- 第七步:处理器适配器向前端控制器返回
ModelAndView
,ModelAndView
是SpringMVC
框架的⼀个底层对象,包括Model
和View
- 第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
- 第九步:视图解析器向前端控制器返回
View
- 第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在ModelAndView对象中填充到
request
域 - 第⼗⼀步:前端控制器向⽤户响应结果
2.2. Spring MVC九大组件
- HandlerMapping(处理器映射器)
HandleMapping
是用来查找Handler
(处理器)的,Handler
可以是一个类也可以是一个方法,标注了@RequestMapping
注解的方法就是一个Handler
。Handler
负责具体实际的请求处理,HandleMapping
的作用就是请求到来时,查找到请求相应的处理器Handler
和Interceptor
。 -
HandlerAdapter(处理器适配器)
HandlerAdapter
是一个处理器适配器,因为Spring MVC
中的Handler
是任意形式的,只要能处理请求就可以,而把请求交给Servlet
,Servlet
的方法结构都是doService(HttpServletRequest request,HttpServletResponse response)
形式的,要让固定的Servlet
处理方法调用Handler
来进行处理,便是HandlerAdapter
的指责。 -
HandlerExceptionResolve(处理器异常解析器)
HandlerExceptionResolve
是处理Handler异常情况的。它的作⽤是根据异常设置ModelAndView
,之后交给渲染⽅法进⾏渲染,渲染⽅法会将ModelAndView
渲染成⻚⾯。 -
ViewResolve(视图解析器)
⽤于将String类型的视图名和
Locale
解析为View
类型的视图,只有⼀个resolveViewName()
⽅法。ViewResolver
在这个过程主要完成两件事情:ViewResolver
找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到视图的类型,如JSP
)并填⼊参数。默认情况下,Spring MVC
会⾃动为我们配置⼀个InternalResourceViewResolver
,是针对JSP
类型视图的。 -
RequestToViewNameTranslator(请求转视图名转换器)
它的作用是从请求中获取
ViewName
,因为ViewResolve
是根据ViewName
来查找View
,但有时候Handler
处理完后没有设置View
,也没有设置ViewName
,这时就需要这个组件从请求中查找ViewName
。 -
LocaleResolve(国际化解析器)
用来处理国际化的一些东西。
ViewResolver
组件的resolveViewName
⽅法需要两个参数,⼀个是视图名,⼀个是Locale
。LocaleResolver
⽤于从请求中解析出Locale
,⽐如中国Locale
是zh-CN
,⽤来表示⼀个区域。这个组件也是i18n
的基础。 -
ThemeResolve(主题解析器)
ThemeResolver
组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。Spring MVC
中⼀套主题对应⼀个properties
⽂件,⾥⾯存放着与当前主题相关的所有资源,如图⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资源设置进去,放在classpath
下,之后便可以在⻚⾯中使⽤了。SpringMVC
中与主题相关的类有ThemeResolver
、ThemeSource
和Theme
。ThemeResolver
负责从请求中解析出主题名,ThemeSource
根据主题名找到具体的主题,其抽象也就是Theme
,可以通过Theme
来获取主题和具体的资源。 -
MultipartResolve(多元素解析器)
MultipartResolver
⽤于上传请求,通过将普通的请求包装成MultipartHttpServletRequest
来实现。MultipartHttpServletRequest
可以通过getFile()
⽅法直接获得⽂件。如果上传多个⽂件,还可以调⽤getFileMap()
⽅法得到Map<FileName,File>
这样的结构,MultipartResolver
的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。 -
FlashMapManager
FlashMap
⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post
请求之后重定向到⼀个get
请求,这个get
请求可以⽤来显示订单详情之类的信息。
这样做虽然可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通过FlashMap
来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过ServletRequestAttributes.getRequest()
⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中
,这样在重定向之后的Handler
中Spring
就会⾃动将其设置到Model
中,在显示订单信息的⻚⾯上就可以直接从Model
中获取数据。FlashMapManager
就是⽤来管理FalshMap
的。
2.3. Spring MVC中的url-pattern的配置和原理剖析
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
方式一:带后缀,比如*.action, *.do, *.aaa
方式二:/ 不包括.jsp
方式三:/* 拦截所有,包括.jsp
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
-
问题:
上面说到
<url-pattern>
有三种配置方式,其中第二种配置为/
,不会拦截.jsp,但是它也存在一些问题,那就是,它会拦截.html
等静态资源(静态资源:除了servlet和jsp之外的js、css、pnd等)。 -
原因:
这是因为这个配置覆写了web应用服务器(tomcat)的默认配置
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
-
解决方案有两种:
-
- 静态资源配置,方案一:如果是静态资源,交给web应用服务器去处理
-
<!--静态资源配置,方案一:如果是静态资源,交给web应用服务器去处理-->
<!--
原理:添加该标签配置之后,会在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler,
其作用是对进入DispatcherServlet的url进行筛查,
若发现是一个静态资源,转由web应用服务器(tomcat)默认的DefaultServlet来处理,
若不是静态资源,继续由Spring MVC框架处理.
-->
<mvc:default-servlet-handler/>
* 1. 静态资源配置,方案二:Spring MVC框架自己处理静态资源
<!--
静态资源配置,方案二:Spring MVC框架自己处理静态资源
mapping: 约定的静态资源url规则
location: 指定的静态资源存放位置
-->
<mvc:resources mapping="/resources/**" location="classpath:/"/>
2.4. Spring MVC的数据输出机制
SpringMVC
在handler
方法上传入Map
、Model
或ModelMap
参数,并向这些参数中保存数据(放入到请求域),都能在页面获取到,原因是底层的数据封装是用的BindingAwareModelMap
类,而BindingAwareModelMap
类域Map
、Model
和MpdelMap
的关系是这样的:
3. 请求参数绑定
SpringMVC
对原⽣servlet api的⽀持如果要在
SpringMVC
中使⽤servlet
原⽣对象,⽐如HttpServletRequest
\HttpServletResponse
\HttpSession
,直接在Handler
⽅法形参中声明使⽤即可。-
SpringMVC
接收简单数据类型参数- 接收简单数据类型参数,直接在handler⽅法的形参中声明即可,框架会取出参数值然后绑定到对应参数上。
- 要求:传递的参数名和声明的形参名称保持⼀致
- 绑定Pojo类型参数
- 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名⽆所谓。
- 要求:传递的参数名必须和Pojo的属性名保持⼀致
- 绑定Pojo包装对象参数
- 不管包装Pojo与否,它⾸先是⼀个pojo,那么就可以按照上述pojo的要求来
- 绑定时候直接形参声明即可
- 传参参数名和pojo属性保持⼀致,如果不能够定位数据项,那么通过属性名 + “.” 的⽅式进⼀步锁定数据
- 绑定⽇期类型参数(需要配置⾃定义类型转换器)
- ⾃定义类型转换器
package com.lzx.converter;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* ⾃定义日期类型转换器
*/
public class DateConverter implements Converter<String, Date> {
public Date convert(String source) {
// 完成字符串向⽇期的转换
SimpleDateFormat simpleDateFormat = new
SimpleDateFormat("yyyy-MM-dd");
try {
Date parse = simpleDateFormat.parse(source);
return parse;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
* 注册自定义的类型转换器
<!--自动注册最合适的处理器映射器,处理器适配器-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--注册⾃定义类型转换器-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.lzx.converter.DateConverter"/>
</set>
</property>
</bean>
4. 对 Restful ⻛格请求⽀持
4.1. 什么是RESTful
示例:
rest是⼀个url请求的⻛格,基于这种⻛格设计请求的url
没有rest的话,原有的url设计 http://localhost:8080/user/queryUserById.action?id=3
url中定义了动作(操作),参数具体锁定到操作的是谁
有了rest⻛格之后
rest中,认为互联⽹中的所有东⻄都是资源,既然是资源就会有⼀个唯⼀的uri标识它,代表它
http://localhost:8080/user/3 代表的是id为3的那个⽤户记录(资源)
锁定资源之后如何操作它呢?常规操作就是增删改查
根据请求⽅式不同,代表要做不同的操作
get 查询,获取资源
post 增加,新建资源
put 更新
delete 删除资源
4.2. (请求方式转换过滤器)表单中使用put和delete方法
由于表单中form
表单中只允许get
和post
方法,所以需要在web.xml
中配置请求⽅式过滤器(将特定的post请求转换为put和delete请求)
- 第一步,表单中添加一个
method
隐藏参数
<form method="post" action="/demo/handle/15/lisi">
<!--隐藏参数指定方法-->
<input type="hidden" name="_method" value="put"/>
<input type="submit" value="提交rest_put请求"/>
</form>
- 第二步,在
web.xml
中配置请求⽅式过滤器
<!--配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求⽅式进⾏转换-->
<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>
第二章 Spring MVC高级技术
1. 拦截器的使用
1.1. 拦截器、过滤器、监听器对比
- Servlet:处理Request请求和Response响应
- 过滤器(Filter):对Request请求起到过滤的作用,作用在Servlet之前,配置为
/*
可以过滤所有的资源(Servlet、js/css等静态资源)。 - 监听器(Listener):实现了
javax.servlet.ServletContextListener
接口的服务器端组件,随web容器启动,只初始化一次,随后会一直运行监听,随着web容器应用的停止而销毁。- 作用一:做⼀些初始化⼯作,比如web应⽤中spring容器启动
ContextLoaderListener
- 作用二:监听web中的特定事件,⽐如
HttpSession
,ServletRequest
的创建和销毁;变量的创建、销毁和修改等。
- 作用一:做⼀些初始化⼯作,比如web应⽤中spring容器启动
- 拦截器(Inteceptor):
是SpringMVC、Struts等表现层框架自己的组件,不会拦截静态资源,只会拦截访问的控制器方法Handler。
从配置的角度看,- Servlet、Filter和Listener是配置在web.xml中的
- ⽽interceptor是配置在表现层框架⾃⼰的配置⽂件中的
拦截次数:三次
- 在Handler业务逻辑执⾏之前拦截⼀次
- 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次
- 在跳转⻚⾯之后拦截⼀次
- 执行流程
1.2. 单个拦截器执行流程
-
- 首先执行拦截器的
preHandle
方法,返回true
则继续向下执行Handler
处理器,否则不再向下执行;
- 首先执行拦截器的
-
Handler
执行完毕后,视图未渲染之前,会执行postHandle
方法;
-
- 在视图渲染完毕后,会执行
afterCompletion
方法。
- 在视图渲染完毕后,会执行
1.3. 多个拦截器执行流程
假设有两个拦截器Interceptor1
和Interceptor2
,并且在配置⽂件中,Interceptor1
拦截器配置在前,如上图所示,preHandle
方法按照配置顺序执行,postHandle
和afterCompletion
方法按照配置的相反顺序执行。
2. 文件上传(处理multipart形式的数据)
- 引入文件上传所需jar
<!--⽂件上传所需jar坐标-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
- 配置文件上传解析器
<!--配置文件上传解析器,id固定multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传大小,单位字节-->
<property name="maxUploadSize" value="1000000000"/>
</bean>
3. 异常处理器
异常处理器需要实现HandlerExceptionResolver接口,现在我们一般使用注解的方式
package com.lzx.controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 优雅的全局异常处理
*/
@ControllerAdvice
public class GlobalExceptionResolver {
@ExceptionHandler(ArithmeticException.class)
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg",e.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
4. 基于Flash属性的跨重定向请求数据传递
重定向参数丢失有两种解决方案
- 第一:参数上手动拼接参数
return "redirect:handle01?name=" + name;
但这种方式属于get请求,参数长度受限制,而且安全度不高。
- 第二:使用SpringMVC提供的flash属性机制,向上下⽂中添加flash属性,框架会在session中记录该属性值,当跳转到⻚⾯之后框架会⾃动删除flash属性,不需要我们⼿动删除,通过这种⽅式进⾏重定向参数传递,参数⻓度和安全性都得到了保障
/**
* SpringMVC 重定向时参数传递的问题
* 转发:A 找 B 借钱400,B没有钱但是悄悄的找到C借了400块钱给A
* url不会变,参数也不会丢失,⼀个请求
* 重定向:A 找 B 借钱400,B 说我没有钱,你找别⼈借去,那么A ⼜带着400块的借钱需求找到C
* url会变,参数会丢失需要重新携带参数,两个请求
*/
@RequestMapping("/handleRedirect")
public String handleRedirect(String name, RedirectAttributes redirectAttributes) {
// return "redirect:handle01?name=" + name; // 拼接参数安全性、参数⻓度都有局限
// addFlashAttribute⽅法设置了⼀个flash类型属性,该属性会被暂存到session中,在跳转到⻚⾯之后该属性销毁
redirectAttributes.addFlashAttribute("name",name);
return "redirect:handle01";
}
第三章 手写MVC框架
第四章 Spring MVC源码深度剖析
第五章 SSM整合
第六章 附录
1. 乱码问题
- post请求乱码,web.xml中加⼊过滤器
<!-- 解决post乱码问题 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<!-- 设置编码参是UTF8 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- get请求乱码,修改tomcat下server.xml的配置
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>