MVC分别是:模型Model(javabean)、视图View(jsp)、控制器Controller(servlet)。
模型(Model):负责数据逻辑的处理和实现数据操作。
视图(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、页面设计等。
控制器(Controller):负责接收转发请求,对请求进行处理后,指定试图并将响应结果发送给客户端。
MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。
1.2 JSP+JavaBean在以往,我们用到的是JSP+JavaBean设计模式,适合一些业务流程比较简单的Web程序。
- JSP(视图):用于处理用户请求
- JavaBean(模型):用于封装和处理数据
工作流程: 浏览器端发出request请求,JSP从请求中获取需要的数据,交由JavaBean进行业务处理,JavaBean通过与数据库的交互获取相关返回值。JavaBean将结果以response形式返还给JSP,最终解析在浏览器。
JSP+JavaBean弊端:
- JSP与JavaBean间严重耦合,维护困难
- JSP责任太多,结构较为混乱,无法实现业务流程复杂的应用
而现在,我们用到的是Servlet+JSP+JavaBean设计模式,也就是MVC模式。
- Servlet(Controller):处理用户请求
- JSP(View):数据显示
- JavaBean(Model):数据封装
该模式将控制层单独划分出来负责业务流程的控制,接收request请求,实例化所需要的JavaBean,并将处理后的数据以response返回给JSP惊醒页面数据展示。
1.4 MVC优缺点优点:
- 多视图可共享同一个模型,提高代码的重用性
- 业务划分清晰,MVC模块相互独立,松耦合框架
- 易于维护,易于扩展大型复杂的web应用
缺点:
- 提高了系统的负责度
- 试图对模型数据的低效率访问
Spring MVC是Spring framework的一部分,是基于Java实现MVC的轻量级Web框架,本质上是Servlet。
Spring MVC是Servlet+JSP+JavaBean的实现,与Spring无缝对接,是如今主流的Web开发框架。
Spring MVC框架曹勇松耦合可拔插的组件结构,具有高度可配置性,相对其其他的框架更具扩展性和灵活性。
2.2 Spring MVC 特点- 角色划分清晰,Model、View、Controller
- 配置灵活,可把类当作Bean通过xml配置
- 与Spring无缝对接
DispatcherServlet(前置控制器):Spring MVC框架是围绕DispatcherServlet设计的,这种Servlet用来处理所有的HTTP请求和响应,支持配置处理器映射、试图渲染、本地化与文件上传等功能。
- 通过 HandlerMapping(处理器映射器)将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器)。
- 通过 HandlerAdapter(适配器) 支持多种类型的处理器(HandlerExecutionChain中的处理器)。
- 通过 ViewResolver(视图解析器) 解析逻辑视图名到具体视图实现。
DispatcherServlet通过与Spring MVC核心组件的相互协作,完成了请求和响应的处理。暂且就叫核心组件吧,但是在官网上定义为 Special Bean Types,因为在程序中我们需要以bean的方式配置它们。
处理流程相当繁琐,不如先搞懂每一各组件的功能,工作流程放后面再说。
2.4.1 HandlerMapping 2.4.1.1 HandlerMapping功能 功能:根据请求匹配到对应的 Handler,然后将找到的 Handler 和所有匹配的 HandlerInterceptor(拦截器)绑定到创建的 HandlerExecutionChain 对象上并返回。
返回值:Handler
2.4.1.2 HandlerMapping接口该接口内容如下,其中主要包含getHandler抽象方法,该方法最终返回了一个HandlerExecutionChain实例。
2.4.1.3 HandlerMapping实现类该接口的实现类如下(选中的是下文中我们要用到的):
可以看到,大致上分为两大类 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping,且都继承自 AbstractHandlerMapping 抽象类。
⚠️ 注意:不同的映射处理器(HandlerMapping) 映射出来的 handler 对象是不一样的,AbstractUrlHandlerMapping 映射器映射出来的是 handler 是 Controller 对象,AbstractHandlerMethodMapping 映射器映射出来的 handler 是 HandlerMethod 对象。
2.4.2 HandlerExecutionChain 2.4.2.1 HandlerExecutionChain功能HandlerExecutionChain与HanderMapping关系非常紧密,HandlerExecutionChain只能通过HanderMapping接口中的唯一方法来获得。
功能:处理程序执行链,由处理程序对象和任何处理程序拦截器组成。根据url查找控制器,下文中url被查找控制器为:hello
组成:实例封装了一个handler处理对象和一些interceptors
2.4.2.2 HandlerExecutionChain类当用户请求到到DispaterServlet中后,配置的HandlerMapping会根据用户请求(也就是handler)会将它与所有的interceptors封装为HandlerExecutionChain对象。
在该类中是这样定义HandlerExecutionChain的:
Handler execution chain, consisting of handler object and any handler interceptors. Returned by HandlerMapping’s HandlerMapping.getHandler method.
该类的内容如下:
2.4.2.3 HandlerExecutionChain实例HandlerExecutionChain实例的获取写在AbstractHandlerMapping类中,由下图的getHandlerExecutionChain方法获取该实例。
getHandlerExecutionChain方法如下:
2.4.3 HandlerAdapter在上文中,提到HandlerMapping不同实现类映射的handler不同,各种处理器中的处理方法各不相同,Spring为了解决适应多种处理器,定义了处理器适配器的概念,也就是我们所说的HandlerAdapter。
2.4.3.1 HandlerAdapter功能在Spring MVC中可以支持多种处理器(处理器也就是处理用户请求的程序)。
Spring实现的处理器类型有Servlet、Controller、HttpRequestHandler以及注解类型的处理器。
功能:帮助DispatcherServlet调用映射到请求的处理程序,而不管处理程序实际是如何调用的(规则要求)。 通过HandlerAdapter对处理器进行执行。
返回值:返回一个ModelAndView对象(包含模型数据、逻辑视图名);
2.4.3.2 HandlerAdapter接口HandlerAdapter接口的内容如下:
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
handle方法:使用给定的处理程序来处理此请求,返回ModelAndView。
2.4.3.3 HandlerAdapter实现类HandlerAdapter接口的实现类如下:
- SimpleServletHandlerAdapter 适配Servlet处理器
- HttpRerquestHandlerAdapter 适配HttpRequestHandler处理器
- RequestMappingHandlerAdapter 适配注解处理器
- SimpleControllerHandlerAdapter 适配Controller处理器(非默认使用)
在下文中,我们用到的是第四种,来具体看下SimpleControllerHandlerAdapter 实现类里面的内容:
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
handle方法:该实现类实现了接口的方法,同样返回ModelAndView
⚠️ 注意:该方法的返回值实际上是Controller执行的handler方法!!
2.4.4 ControllerController接口被显式设计为操作HttpServletRequest和HttpServletResponse对象。
就像HttpServlet一样。
2.4.4.1 Controller功能 功能:具体实现handler方法,
返回值:返回一个ModelAndView对象(包含模型数据、逻辑视图名);
2.4.4.2 Controller接口Controller接口基本没有上面内容(也没找到实现类),只有一个handleRequest方法:
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpaServletRequest request, HttpServletResponse response) throws Exception;
}
handleRequest方法:处理请求并返回一个ModelAndView对象,DispatcherServlet将呈现该对象。
⚠️ 注意:返回值为null不是错误,它表明该对象本身完成了请求处理,因此没有ModelAndView来呈现。
2.4.4.3 Controller实现类Controller接口的实现类如下(最下面选中的是下文中自己写的实现类):
2.4.4.4 ModelAndView对象ModelAndView对象中有两个主要的方法:
public class ModelAndView {
public ModelAndView addObject(String attributeName, @Nullable Object attributevalue)
{
getModelMap().addAttribute(attributeName, attributevalue);
return this;
}
public void setViewName(@Nullable String viewName) {
this.view = viewName;
}
}
addObject可以看作一个session,就像之前定义session.setAttribute那样,name、value就对应session的key-value。
2.4.5 ViewResolver 2.4.5.1 ViewResolver功能 功能:解析DispatcherServlet传递的逻辑视图名,解析为具体的View,并将解析结果传回给DispatcherServlet。
2.4.5.2 ViewResolver接口ViewResolver接口中只有一个返回视图名的resolveViewName抽象方法。
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
resolveViewName方法:按名称解析给定的视图。
- viewName:要解析的视图的名称
- locale:用于解析视图的区域设置。
ViewResolver接口的实现类如下(选中的是下文中我们要用到的):
- ResourceBundleViewResolver:采用bundle根路径所指定的ResourceBundle中的bean定义作为配置。
- XmlViewResolver:该类接受一个XML格式的配置文件。
- URLbasedViewResolver:它直接使用URL来解析到逻辑视图名,除此之外不需要其他任何显式的映射声明。
- FreeMarkerViewResolver :最终会解析逻辑视图配置,返回 freemarker 模板。不需要指定 viewClass 属性。
- ContentNegotiatingViewResolver:它会根据所请求的文件名或请求的Accept头来解析一个视图。
2.4.5.4 InternalResourceViewResolverInternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLbasedViewResolver 的子类,拥有 URLbasedViewResolver 的一切特性。
InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。
InternalResourceViewResolver 是实现类的主要内容如下:
public class InternalResourceViewResolver extends UrlbasedViewResolver {
//将默认视图类设置为requiredViewClass:默认情况下是InternalResourceView,如果JSTL API存在,则默认是JstlView。
public InternalResourceViewResolver() {
Class> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
public InternalResourceViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}
@Override
protected AbstractUrlbasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
}
在其父类URLbasedViewResolver 中,buildView方法如下:
protected AbstractUrlbasedView buildView(String viewName) throws Exception{..}
AbstractUrlbasedView:基于url的视图的抽象基类。以“URL”bean属性的形式提供一致的方式来保存View包装的URL。
2.4.6 ViewView是最后一个处理环节了!!
2.4.6.1 View功能 功能:View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术,实现类支持不同的View类型(jsp、freemarker、pdf…)
2.4.6.1 View接口view接口的抽象内容如下:
void render(@Nullable Mapmodel, HttpServletRequest request, HttpServletResponse response) throws Exception;
可以看到,通过Map数据结构支持给定模型呈现视图。
2.4.6.1 View实现类该接口的实现类如下:
3. DispatcherServlet通过对Spring MVC中核心组件的认识,我们应该看到了DispatcherServlet在这之中扮演的角色。
下面根据源码,将DispatcherServlet的交互过程再梳理一遍。
1️⃣ Step1:request -> HandlerMapping.getHandler() -> HandlerExecutionChain对象(Handler)
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
2️⃣ Step2:Handler -> HandlerAdapter -> ModelAndView
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
3️⃣ Step3:ModelAndView -> View -> Response
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
4. Spring MVC 执行原理
通过上文描述,应该对Spring MVC有了简单的认识,下面梳理下整体的工作流程。
1️⃣ Step1:DispatcherServlet接受浏览器请求、拦截请求;
2️⃣ Step2:DispatcherServlet调用HandlerMapping,HandlerMapping根据请求URL查找Handler;
3️⃣ Step3:HandlerExecutionChain处理程序执行链,根据URL查找控制器;
4️⃣ Step4:HandlerExecutionChain将查找到的具体的Handler解析后返回Handler;
5️⃣ Step5:DispatcherServlet调用HandlerAdapter处理器适配器,按照特定的规则去执行Handler。
6️⃣ Step6:HandlerAdapter经过适配调用具体的处理器Controller;
7️⃣ Step7:Handler由Controller执行,执行后的具体信息(ModelAndView)返回给HandlerAdapter;
8️⃣ Step8:HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet;
9️⃣ Step9: DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
Step10:视图解析器将解析的逻辑视图名(具体的View)返回给DispatcherServlet;
1️⃣1️⃣ Step11:DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图;
1️⃣2️⃣ Step12:将View作为Request返回给历览器。
5. Spring MVC 模型看完理论部分,不如来实际写一个Model。
官方文档:spring-web
5.1 前期准备JDK:1.8
Maven:3.8.2
IDEA:2021.2
Tomcat:9.0.46
新建一个Maven项目,删去src目录,再项目下新建一个空的Model。
导入核心依赖:
- spring-webmvc :org.springframework
- servlet-api :javax.servlet
- jsp-api :javax.servlet
- jstl :javax.servlet
在Model名称右键Add frameworl Support...,勾选Web Application 。
配置Tomcat参考:IDEA配置Tomcat
5.3 配置web.xml 5.3.1 配置Servlet我们知道DispatcherServlet本质是一个Servlet,所以需要像往常一样在web.xml文件中配置Servlet映射。
SpringMVC org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:SpringMVC-servlet.xml 1 SpringMVC /
在这里面,出现了不认识的内容,下面展开看看。
5.3.2 配置Spring MVC 配置文件contextConfigLocation classpath:SpringMVC-servlet.xml
这里是配置一个contextConfigLocation,在
5.3.3 配置启动级别如果是 标签内的,如果没有提供值,默认会去找/WEB-INF/applicationContext.xml
- 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
- 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
5.3.4 匹配JSP1
/ 匹配所有的请求;(不包括.jsp)
/* 匹配所有的请求;(包括.jsp)
5.4 编写Spring MVC 配置文件/
5.4.1 处理映射器
5.4.2 处理器适配器
5.4.3 视图解析器
- prefix:前缀
- suffix:后缀
5.5 Controller
前面提到,相当于一个Servlet的组件是Controller。我们的操作业务写在这里面。
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 实例化模型和视图
ModelAndView modelAndView = new ModelAndView();
// 封装对象,放在ModelAndView中
modelAndView.addObject("username", "welcome");
// 跳转页面,全路径为:/jsp/test.jsp
modelAndView.setViewName("test");
return modelAndView;
}
}
5.6 注册Controller
有了Control后,我们需要将它交给SpringIOC容器,所以我们需要将其注册为Bean。
在SpringMVC-servlet.xml中注册Bean。
5.7 JSP页面
写要跳转的jsp页面,显示ModelandView存放的数据,以及我们的正常页面。
注意之前定义的前缀,JSP路径为web/jsp/test.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
${username}
username:这里利用EL表达式取到的username,相当于之前的session.setAttribute
5.8 测试启动Tomcat,在修改地址栏为http://localhost:8080/test
6. 写在最后上面Spring MVC Model的代码量其实并不多,但在实际应用中我们也并不会写这么代码,后期通过简单的注解就能实现相同的功能。重要的是理解Spring MVC 的执行过程,这也是面试点。
Spring MVC 中的各个组件容易搞混淆,下面将Model中到的实现类以及父类接口再梳理一遍。
| 接口 | 实现类 | 说明 |
|---|---|---|
| Null | DispatcherServlet | 前置控制器 |
| HandlerMapping | BeanNameUrlHandlerMapping | 处理映射器 |
| HandlerAdapter | SimpleControllerHandlerAdapter | 处理适配器 |
| ViewResolver | InternalResourceViewResolver | 视图解析器 |
DispatcherServlet:org.springframework.web.servlet.DispatcherServlet
BeanNameUrlHandlerMapping:org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
SimpleControllerHandlerAdapter:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
InternalResourceViewResolver:org.springframework.web.servlet.view.InternalResourceViewResolver
❤️ END ❤️



