SpringMVC
1、MVC
-
什么是MVC?
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。
M:Model,模型层,指工程中的JavaBean,作用是处理数据。
JavaBean分为两类:
- 一类称为实体类Bean:专门存储业务数据的,如Student、User等
- 一类称为业务处理Bean:指Service或Dao对象,专门用于处理业务逻辑和数据访问
V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据。
C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程:用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。
2、什么是SpringMVC?
-
什么是SpringMVC?
SpringMVC是Spring的一个后续产品,是Spring的一个子项目。SpringMVC是Spring为表述层开发提供的一整套完备的解决方案。在表述层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为JavaEE项目表述层开发的首选方案。
三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet
-
为什么需要SpringMVC?
- Spring家族原生产品,与IOC容器等基础设施无缝对接
- 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
- 表述层细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程序高,可插拔式组件即插即用,想要什么功能配置相应组件即可
- 性能卓著,尤其适合现代大型、超大型互联网项目要求
-
如何使用SpringMVC?
- 创建maven工程
- 添加web模块
- 打包方式:war
- 引入依赖
- 配置web.xml
- 创建maven工程
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-
class>
<!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
<init-param>
<!-- contextConfigLocation为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的
src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求-->
<url-pattern>/</url-pattern>
</servlet-mapping
<url-pattern>标签中使用/和/*的区别:
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径,但是/不能匹配.jsp请求路径的请求,因此就可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面
/则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/的写法
1. 创建请求控制器
2. 创建SpringMVC的配置文件
<!-- 自动扫描包 -->
<context:component-scan base-package="com.atguigu.mvc.controller"/>
<!-- 配置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>
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
1. 测试HelloWorld
3、SpringMVC支持ant风格的路径
?:表示任意的单个字符
* :表示任意的0个或多个字符
** :表示任意的一层或多层目录
注意:在使用 ** 时,只能使用/**/xxx的方式
4、SpringMVC支持路径中的占位符(重点)
-
原始方式:/deleteUser?id=1
-
rest方式:/deleteUser/1
SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,在通过@PathVariable注解,将占位符所表示的数据赋值给控制器的方法的形参。
<a th:href="@{/testRest/1/admin}">测试路径中的占位符-->/testRest</a><br>
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id,@PathVariable("username") String username){
System.out.println("id:" + id + ", username:" + username);
return "success";
}
//最终输出的内容为 --> id:1, username:admin
5、域对象共享数据
-
想要域对象共享数据使用哪些ServletAPI?
- 使用ModelAndView向request域对象共享数据
- 使用Model向request域对象共享数据
- 使用Map向request域对象共享数据
- 使用ModelMap向request域对象共享数据
-
Model、ModelMap、Map的关系?
Model、ModelMap、Map类型的参数其实实质上都是BindingAwareModelMap类型的
public interface Model{}
public class ModelMap extends LinkedHashMap<String,Object>{}
public class ExtendedModelMap extends ModelMap implements Model{}
public class BindingAwareModelMap extends ExtendedModelMap{}
- 共享数据域
- 向session域共享数据
- 向application域共享数据
6、SpringMVC的视图
-
什么是视图?
SpringMVC中的视图是View接口,种类有很多,默认有转发视图和重定向视图。
① 当工程引入jstl的依赖,转发视图会自动转换为jstlView。
② 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后得到的是ThymeleafView。
-
为什么需要视图?
视图的作用是渲染数据,将模型Model中的数据展示给用户。
-
ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示。
<!--
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
<mvc:annotation-driven />
7、RESTful(重点)
-
什么是RESTful?
REST:Representational State Transfer,表现层资源状态转移。
REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。
REST原则:
- 对网络上所有的资源都有一个资源标识符
- 对资源的操作不会改变标识符
- 同一资源有多种表现形式(xml、json)
- 所有操作都是无状态的(Stateless)
1)URI和URL的区别:
URI: http://example.com/users/
URL: http://example.com/users/{user}(one for each user)
2)什么是无状态性:
使得客户端和服务器端不必保存对方的详细信息,服务器只需要处理当前的请求,不需了解
请求的历史。
可以更容易的释放资源,让服务器利用Pool(连接池)技术来提高稳定性和性能。
- RESTful的实现
操作 | 传统方式 | REST风格 |
查询操作 | getUserById?id=1 | user/1 —> get请求方式 |
保存操作 | saveUser | user —> post请求方式 |
删除操作 | deleteUser?id=1 | user/1 —> delete请求方式 |
更新操作 | updateUser | user —> put请求方式 |
**使用HiddenHttpMethodFilter**
由于浏览器只支持发送get和post方式的请求
SpringMVC提供了HiddenHttpMethodFilter帮助我们将POST请求转换为DELETE或PUT请求
HiddenHttpMethodFilter处理put和delete请求的条件:
① 当前请求的请求方式必须为post(即表单提交)
② 当前请求必须传输请求参数_method
<form action="/user" method="post">
<input type="hidden" name="_method" value="put" />
</form>
- 案例架构
功能 | URL地址 | 请求方式 |
访问首页 | / | GET |
查询全部数据 | /employees | GET |
删除 | /employee/2 | DELETE |
跳转到添加数据页面 | /toAdd | GET |
执行保存 | /employee | POST |
跳转到更新数据页面 | /employee/2 | GET |
执行更新 | /employee | PUT |
1)控制器方法
@Autowired
EmployeeDao employeeDao;
@RequestMapping(value="/employees",method=RequestMethod.GET)
public String getEmployeeList(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList",employeeList);
return "employee_list";
}
2)创建employee_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Employee Info</title>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align:center;" id="dataTable">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
</tr>
<tr th:each="employee:${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
<a th:href="@{'/employee/'+${employee.id}}">update</a>
</td>
</tr>
</table>
</body>
</html>
8、HttpMessageConverter
-
什么是HttpMessageConverter?
HttpMessageConverter,报文信息转换器,将请求报文转换为java对象,或将java对象转换为响应报文
-
怎么使用HttpMessageConverter?
HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity
1)@RequestBody
@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值
<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username" /> <br/>
密码:<input type="password" name="password" /> <br/>
<input type="submit"/>
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody : "+ requestBody);
return "success";
}
输出结果:
requestBody :username=admin&password=123456
**2)RequestEntity**
> RequestEntity封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过**getHeaders()**获取请求头信息,通过**getBody()**获取请求体信息。
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:" + requestEntity.getHeaders());
System.out.println("requestBody:" + requestEntity.getBody());
return "success";
}
输出结果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27",cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "GoogleChrome";v="90"", sec-ch-ua-mobile:"?0", upgrade-insecure-requests:"1".......
3)@ResponseBody
> @ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应体响应到浏览器。
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
输出结果:
浏览器页面显示success
> @ResponseBody还可以和@Controller合成一个注解@RestController
**4)ResponseEntity**
> ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
9、拦截器
-
拦截器的配置
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<mvc:interceptor>
<mvc:mapping path="/**/" >
<mvc:exclude-mapping path="/testRequestEntity" />
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的
请求,通过mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->
-
拦截器的三个抽象方法
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值标识是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
- postHandle:控制器方法执行之后执行postHandle()
- afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行**若某个拦截器的preHandle()返回了false
**preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
10、异常处理器
- 基于配置的异常处理
//SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
//HandlerExceptionResolver接口的实现类有:
//DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
//SpringMVC提供了自定义的异常处理器SimpleMappingEexceptionResolver
<bean class="org.springframework.web.servlet.handler.simpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
- 基于注解的异常处理
//@ControllerAdvice 将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController{
//@ExceptionHandler用于设置所标识方法处理的异常
@ExceptionHandler(ArithmeticException.class)
//ex表示当前请求中处理中出现的异常对象
public String handleArtimeticException(Exception ex, Model model){
model.addAttribute("ex",ex);
return "error";
}
}
11、SpringMVC执行流程
-
SpringMVC常用组件
-
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其他组件处理用户的请求
-
HandlerMapping:处理器映射器,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法
-
Handler:处理器,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
-
HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
-
ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
-
View:视图
作用:将模型数据通过页面展示给用户
-
-
DispatcherServlet初始化过程
DispatcherServlet本质上是一个Servlet,所以天然的遵循Serlvet的生命周期。所以宏观上是Servlet生命周期来进行调度。
A→初始化WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
B→创建WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
C→DispatcherServlet初始化策略
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherSerlvet的各个组件。 所在类:org.springframework.web.serlvet.DispatcherServlet
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
-
DispatcherServlet调用组件处理请求
A→processRequest()
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request,response) 所在类:org.springframework.web.servlet.FrameworkServlet
B→doService()
所在类:org.springframework.web.servlet.DispatcherServlet
C→doDispatch()
所在类:org.springframework.web.servlet.DispatcherServlet
D→processDispatchResult()
-
SpringMVC的执行流程
1)用户向服务器发送请求,请求被SpringMVC前端控制器DispatcherServlet捕获
2)DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
a)不存在 i . 再判断是否配置了**mvc:default-servlet-handler** ii . 如果没配置,则控制台映射查找不到,客户端展示404错误 iii . 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误 b)存在则执行下面的流程
3)根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExceptionChain执行链对象的形式返回
4)DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter
5)如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(...)方法【正向】
6)提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求,在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作
a)**HttpMessageConveter**:将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息 b)数据转换:对请求消息进行数据转换。如String转成Integer、Double等 c)数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等 d)数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
7)Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象。
8)此时将开始执行拦截器的postHandle(...)方法【逆向】
9)根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View来渲染视图。
10)渲染视图完毕执行拦截器的afterCompletion(...)方法【逆向】
11)将渲染结果返回客户端