1.springMVC概述
Spring MVC(Model-View-Controller)是基于 Java 的 Web 应用程序框架,用于开发 Web 应用程序。它通过将应用程序分为模型(Model)、视图(View)和控制器(Controller)三个部分来帮助开发人员实现清晰的代码结构和逻辑分离。概念本质:MVC思想,分层工作模式
模型(Model)负责封装应用程序的业务逻辑和数据 由service、dao、entity
等JavaBean
构成
视图(View)负责展示用户界面 由jsp、html、ftl....
等组成
控制器(Controller)负责处理用户请求、调用业务逻辑并根据结果选择合适的视图进行展示。
早期的servlet开发,不划分模块,代码堆积在少量的不同包下,学了SpringMVC,慢慢适应对功能等按不同规则划分模块,层次。
SpringMVC设计本质:请求作为驱动。前端控制器/DispatcherServlet(核心)接收所有请求,并根据请求的信息将请求 路由/分发 到合适的 Controller 进行处理。根据返回数据DispatcherServlet 还负责调用其他组件 处理视图解析、异常处理、拦截器等功能。
2.SpringMVC使用方式
现在流行SpringBoot,省去很多配置,但是是开发者对 application.yml
中很多技术栈的配置项也并不是特别理解。大多数是复制 他人的配置,会用但不知道使用原因。这时有时间单独对Spring、SpringMVC、Mybatis、Shiro等了解下。
2.1SpringMVC的核心配置文件
配置SpringMVC的核心文件:springmvc.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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 通过context:component-scan元素扫描指定包下的控制器-->
<!-- 扫描com.xxx.xxx及子孙包下的控制器(扫描范围过大,耗时)-->
<context:component-scan base-package="com.xxx.controller"/>
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- viewClass需要在pom中引入两个包:standard.jar and jstl.jar -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 省略其他配置...... -->
</beans>
总结:1.配置包扫描—Controller类所在的路径 2.配置视图解析器
2.2web项目核心文件web.xml配置
<!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>
<display-name>WebTest</display-name>
<!-- 再这里会添加一个SpringMVC的servlet配置项 -->
<servlet>
<!-- 首先指定SpringMVC核心控制器所在的位置 -->
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet启动时,springmvc配置文件加载路径
此参数可以不配置,默认值为:/WEB-INF/springmvc-servlet.xml
可以改为加载类路径下(resources目录),加上classpath:
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- <param-value>/WEB-INF/springmvc-servlet.xml</param-value> -->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--
DispatcherServlet对象创建时间问题
1)默认情况下,第一次访问该Servlet的创建对象,意味着在这个时间才去加载springMVC.xml
2)可以改变为在项目启动时候就创建该Servlet,提高用户访问体验。
<load-on-startup>1</load-on-startup>
数值越大,对象创建优先级越低! (数值越低,越先创建)
-->
<load-on-startup>1</load-on-startup>
<!--web.xml 3.0的新特性,是否支持异步-->
<!--<async-supported>true</async-supported>-->
</servlet>
<!-- 配置路由匹配规则,/ 代表匹配所有,类似于nginx的location规则 -->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
总结:web.xml中针对SpringMVC做了一件事。加一个servlet配置。1.指定DispatcherServlet类的全路径 2.DispatcherServlet启动时,从哪个文件中加载组件的初始化信息 3.配置了路由规则 / 拦截请求的规则 此处除了.jsp都会拦截
2.3配置Controller类
@RestController()
@Api(tags = "Test相关操作")
@Slf4j
public class TestController {
@GetMapping("/v1/test")
@ApiOperation(value = "test功能描述", produces = "application/x-www-form-urlencoded")
public String testProcess (final String id) {
System.out.println(id);
retun id;
}
发送对应请求即可—— http://localhost:8080/v1/test
梳理:
- DispatcherServlet接收到URL请求/v1/test,结合@GetMapping(“/v1/test”)注解把该请求交给testProcess 业务方法进行处理
- 执行testProcess 业务方法,控制台打印日志,并且返回参数id字符串(逻辑视图).
- 结合springmvc.xml中的视图解析器配置,找到目标资源:/id.jsp,即/WEB-INF/jsp/目录下的id.jsp文件,把该JSP资源返回给客户端完成响应。假设参数id为13,则13.jsp资源返给客户端
一般都会将WEB
应用打成war
包,然后放入到Tomcat
中运行,而当Tomcat
启动时,首先会找到对应的WEB
程序,紧接着会去加载web.xml
,加载web.xml
时,由于配置了DispatcherServlet
,所以此时会先去加载DispatcherServlet
,而加载这个类时,又会触发它的初始化方法,会调用initStrategies()
方法对组件进行初始化,如下:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* 初始化此 servlet 使用的策略对象。
* 可以在子类中重写,以便初始化进一步的策略对象
* 各大组件的初始化
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
但是初始化的组件需要改东西,或者说加载一些配置信息,可由 <init-param> 中的 <param-value>classpath:springmvc.xml</param-value>,读取配置信息,对各大组件初始化。
注意:通过配置 contextConfigLocation
参数,开发者可以灵活地指定 DispatcherServlet 的应用程序上下文配置文件,从而实现对 DispatcherServlet 的定制和配置。
3.SpringMVC工作原理
3.1一些常用组件介绍:主为前五点
DispatcherServlet
前端控制器 统一处理接收请求,响应结果。是整个流程控制的中心,由它调用其它组件处理用户的请求 程序员不需要开发HandlerMapping
处理映射器 框架提供,处理器映射器会对用户的请求进行处理,处理成Handler。并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给前端控制器。SpringMVC
提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 映射到对应、符合的Controller(处理器) 程序员不需要开发HandlerAdapter
处理适配器 一个用于执行Handler
处理器 的组件,将Handler处理成ModelAndView逻辑视图,并返给前端控制器。 调用具体的Controller(处理器)处理 程序员不需要开发Handler
处理器 处理请求业务逻辑,在SpringMVC
中会被包装成一个Handler
对象。 默认为controller,其实还包括【HandlerInterceptor/拦截器、HandlerMethodReturnValueHandler /处理 Controller 方法的返回值】 等 程序员开发如controller- ViewResolver视图解析器: 前端控制器会将ModelAndView交给视图解析器进行进一步解析,最终解析成视图进行渲染。比如
controller
方法执行完成之后,return
的值是index
,那么会对这个结果进行解析,将结果生成例如index.jsp
这类的View
视图。 程序员不需要开发View
视图View
在SpringMVC
中是一个接口,实现类支持不同的类型,例如jsp、freemarker、ftl...
,现在一般都是前后端分离的项目,因此也很少再用到这块内容,视图。一般都成了html
页面,数据结果的渲染工作也交给了前端完成 程序员开发jsp页面
3.2流程图例展示(图为截取其他)
观察如上流程,SpringMVC
中的其他组件几乎不存在太多的耦合关系,大部分的工作都是由DispatcherServlet
来调度组件完成的,因此这也是它被称为“中央控制器”的原因,DispatcherServlet
本质上并不会处理用户请求,它仅仅是作为请求统一的访问点,负责请求处理时的全局流程控制。
执行流程如下:(图为截取其他)
4.为什么不用配置多数组件呢?—DispatcherServlet 的默认配置
大概解释:因为DispatcherServlet会读取属性文件——DispatcherServlet.properties
详细解释:以DispatcherServlet中初始化方法 initStrategies 中调用的 initHandlerMappings(context);作为案例
1.private List<HandlerMapping> handlerMappings; 没有配置就是为null,则关注getDefaultStrategies方法
2.看图4.1
DispatcherServlet中有个静态属性 private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
图中 1.读取属性文件【DispatcherServlet.properties】得到 private static Properties defaultStrategies的具体值。
2.3.通过参数strategyInterface的getName()方法 获取到全路径名 key="org.springframework.web.servlet.HandlerMapping";
defaultStrategies.getProperty(key)得到读取的属性文件中对应的值 value。(下属第二张图4.2)
4.创建集合 List<T>strategies,根据循环将 有value转换的String[]className解析到strategies中。(里面存的HandlerMapping类型)
5.返回集合strategies 结束。获取HandlerMapping的过程
图4.1
图4.2
5.SpringMVC请求重定向和转发
-
请求重定向(Redirect):
- 重定向是通过向客户端发送一个特殊的响应(HTTP 302 Found)来告诉客户端去请求另一个 URL。
- 重定向会导致客户端发起一个新的请求,并且地址栏会显示跳转后的新 URL。
- 重定向是一种完全的客户端行为,服务器收到请求后会返回一个重定向状态码,客户端会重新请求新的 URL。
- 在 Spring MVC 中,可以通过
redirect:
前缀来实现重定向,例如:return "redirect:/newUrl"
。 - 原生的 Servlet 开发中,实现 response.sendRedirect("xxx.jsp")
-
请求转发(Forward):
- 转发是在服务器端进行的操作,浏览器端 / 客户端 不会感知到地址的变化。
- 在 Spring MVC 中,可以通过
forward:
前缀来实现请求转发,例如:return "forward:/newUrl"
。 - 原生的 Servlet 开发中,实现request.getRequestDispatcher("xx.jsp").forward()
注意:1.对于请求转发的页面,可以是WEB-INF中页面【服务器访问】;而重定向的页面,是不能为WEB-INF 中页的。因为重定向相当于用户再次发出一次请求,而用户/浏览器是不能直接访问 WEB-INF 中资源的。【安全性高】
2.指定的视图前添加 forward:,且此时的视图不再与视图解析器一同工作
在使用重定向和转发时,需要根据具体的需求和场景来选择合适的方式:
- 如果需要跳转到一个完全不同的 URL 地址,或者需要避免表单重复提交等情况,通常会选择重定向。
- 如果只是希望在服务器内部完成请求的转发,不想让客户端感知到地址的变化,通常会选择转发。
6.SpringMVC异常处理
-
使用
@ControllerAdvice
注解:图6.1@ControllerAdvice
注解可以定义一个全局的异常处理类,用于处理 Controller 层中抛出的异常 。 【service抛出异常,controller调用了service,也能捕获处理】- 在这个类中,可以使用
@ExceptionHandler
注解定义多个方法,每个方法处理不同类型的异常。 - 当 Controller 层中抛出异常时,Spring MVC 会自动寻找合适的异常处理方法,并执行该方法处理异常。
-
实现
HandlerExceptionResolver
接口:图6.2HandlerExceptionResolver
接口是 Spring MVC 中的异常解析器,它可以用于处理所有的异常情况。- 实现这个接口需要重写其中的
resolveException
方法,根据具体的异常类型进行处理。 - 在 Spring MVC 中,如果有多个异常解析器,则会按照顺序依次执行,直到找到一个能够处理异常的解析器为止。
-
在 Controller 层中使用
@ExceptionHandler
注解:图6.3@ExceptionHandler
注解可以用于在 Controller 层中定义异常处理方法,用于处理当前 Controller 中抛出的异常。- 这种方式比较适合处理一些特定的异常,例如表单验证失败等。
图6.1
图6.2
图6.3
7.SpringMVC拦截器
7.1拦截器介绍
Interceptor 拦截器是非常重要的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理
常见应用场景
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等 预处理、后处理。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;预处理
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录)后处理 或 afterCompletion方法;
public class MyInterceptor implements HandlerInterceptor {
@Override//预处理回调方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入controller前拦截 " + request.getRequestURI());
return true; // 返回 true 表示继续执行后续流程,返回 false 表示终止执行后续流程
}
@Override//后处理回调方法
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Controller 方法调用之后被调用,但是会在视图返回被渲染之前被调用 " + request.getRequestURI());
//代码中在controller执行之后,但是返回modelAndView之前执行
}
@Override//整个请求处理完毕回调方法
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("视图渲染完毕时回调: " + request.getRequestURI());
//性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally
}
}
注意:1.preHandle预处理方法返回true,才会放行,而执行postHandle、afterCompletion方法 2.拦截器就是实现HandlerInterceptor这个接口,然后去重写它里面preHandle,postHandle和afterCompletion这三个方法 3.afterCompletion 方法中,请求处理已经完成,响应已经返回给客户端。对 ModelAndView 再操作也对响应无济于事【在 DispatcherServlet 完全处理请求后被调用】 4.如果需要在拦截器中对 ModelAndView
进行修改,通常应该在 preHandle
或 postHandle
方法中进行。在 preHandle
方法中可以修改 ModelAndView
中的模型数据,而在 postHandle
方法中可以修改视图名称等信息。 5.当 preHandle()方法返回 false 时,使用 request 或 response 对请求进行响应 如:请求转发或重定向到登录页面,error错误信息页面。
图解7.1
总结:preHandle——》Handle(Controller)——》postHandle——》afterCompletion
7.2多个拦截器执行顺序
当有多个拦截器时,形成拦截器链。拦截器链的执行顺序,与其注册顺序一致【从上往下看】。需要再次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法
图解7.2
注意:绿色为一个完整MyInterceptor,橙黄色为另一个完整MyInterceptor2。
重点关注:第一个拦截器的 preHandle
方法返回 true
,而第二个拦截器的 preHandle
方法返回 false
,整个拦截器链会被中断,目标处理器(Controller)的方法不会被执行。但是,第一个拦截器的 afterCompletion
方法会被调用。
总结:无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中的afterCompletion()方法。
7.3执行流程图(截图其他)
8.过滤器和拦截器、servlet执行顺序
8.1大致图
8.2细节图解
9.SpringMVC的执行流程
深度剖析SpringMVC的执行流程,看完帮你立即提升一个台阶! - 知乎
10.其余参考资料:
【SSM】Spring MVC 程序开发(重点:SpringMVC 工作流程) - 知乎 (zhihu.com)
Spring MVC详解(学习总结)_springmvc技能经验描述-CSDN博客
过滤器,拦截器,监听器的区别 - Rooker - 博客园 (cnblogs.com)