MVC分层介绍一下
面试官您好,MVC是一种非常经典、影响深远的软件设计模式 ,它的全称是Model-View-Controller。在我看来,它的核心目标就是解决早期Web开发中,业务逻辑、数据和界面显示高度耦合的问题,从而实现"各司其职、互不干扰"。
1. MVC的核心组件与职责
MVC将一个Web应用划分为三个核心部分:
- 模型 (Model) :这是应用的核心,负责处理业务逻辑 和管理数据 。
- 在实践中,我会把它进一步细分为两部分:
- 业务处理逻辑 (Business Logic) :这通常是我们的Service层和DAO层,负责执行具体的业务操作和数据存取。
- 数据载体 (Data Carrier) :这通常是我们的POJO实体类或DTO,它们不包含复杂的业务逻辑,仅仅作为数据的容器,在不同层之间传递。
- 在实践中,我会把它进一步细分为两部分:
- 视图 (View) :这是用户直接看到的界面 ,负责展示数据 。
- 在Web开发中,它通常是一个JSP页面、Thymeleaf模板、或者是现代前后端分离架构中的前端组件(如Vue/React)。View本身非常"笨",它只关心如何把Model传递给它的数据渲染出来给用户看。
- 控制器 (Controller) :这是M和V之间的 "调度中心"或"交通警察" 。
- 它的唯一职责就是接收用户的请求,调用合适的Model来处理这个请求,然后为处理结果选择一个合适的View来展示。它起到了承上启下的作用,确保了Model和View之间的解耦。
2. 在Spring MVC中,这个流程是如何实现的?
Spring MVC框架就是对经典MVC模式的一次完美实现。它的整个工作流程,是围绕着一个核心的前端控制器------ DispatcherServlet
来展开的。
我们可以把一次HTTP请求的旅程,看作以下几个步骤:
- 用户发起请求:用户在浏览器中输入URL,或者提交一个表单。
DispatcherServlet
接收请求 (核心调度) :- 所有的请求首先都会被
DispatcherServlet
这个前端控制器拦截到。它是整个流程的"总指挥"。
- 所有的请求首先都会被
HandlerMapping
查找处理器 :DispatcherServlet
会询问HandlerMapping
(处理器映射器):"这个请求应该由哪个Controller的哪个方法来处理?"HandlerMapping
会根据请求的URL、HTTP方法等信息,找到匹配的、被@RequestMapping
等注解标记的Controller方法。
Controller
执行业务逻辑 :DispatcherServlet
将请求分派给找到的Controller方法。- 在Controller方法中,我们会去调用相应的Service层(Model的一部分) 来执行真正的业务逻辑。
- Model返回处理结果 :
- Service层处理完毕后,会返回数据。Controller会将这些数据,连同要展示的视图信息,封装到一个
ModelAndView
对象 中,并将其返回给DispatcherServlet
。
- Service层处理完毕后,会返回数据。Controller会将这些数据,连同要展示的视图信息,封装到一个
ViewResolver
解析视图 :DispatcherServlet
拿到了ModelAndView
后,会把它交给ViewResolver
(视图解析器) 。ViewResolver
会根据ModelAndView
中指定的逻辑视图名(比如"user/profile"
),解析出真正的物理视图地址(比如/WEB-INF/jsp/user/profile.jsp
)。
View
渲染并响应 :- 最后,
DispatcherServlet
会调用解析出的View(比如一个JSP页面),并将Model中的数据传递给它。 - View负责将这些数据渲染成最终的HTML页面,然后由
DispatcherServlet
响应给用户的浏览器。
- 最后,
总结一下 ,MVC模式通过清晰的职责划分,将Web应用拆分为M、V、C三个松耦合的部分。而Spring MVC框架,则通过DispatcherServlet
作为核心调度器,以及HandlerMapping
、ViewResolver
等一系列可配置的组件,优雅地实现了这一模式,使得我们的Web开发变得更加结构化、可维护和可扩展。
HandlerMapping 和 HandlerAdapter有了解吗?
面试官您好,HandlerMapping
和HandlerAdapter
是Spring MVC 前端控制器( DispatcherServlet
) 的两个最核心、最得力的"助手"。它们共同解决了"一个进来的HTTP请求,究竟应该由谁(哪个Controller)来处理,以及该如何调用它"这个关键问题。
我喜欢用一个 "多功能插座" 的比喻来理解它们的关系:
DispatcherServlet
:就像一个智能插座板,是所有电流(请求)的入口。HandlerMapping
:就像插座板上的 "寻路指示灯" 。- 各种类型的Controller :就像是来自世界各地的、有着不同形状插头的电器(比如两脚的、三脚的、圆头的)。
HandlerAdapter
:就是插在插座板上的 "万能转换头(适配器)"。
1. HandlerMapping
:请求的"导航员"
- 它的核心职责 :"找" 。它的唯一任务,就是根据进来的
HttpServletRequest
信息(主要是URL),从Spring容器中找出应该处理这个请求的那个处理器(Handler)。 - 找到的是什么? 在现代Spring MVC中,这个Handler通常就是一个被
@RequestMapping
等注解标记的Controller方法 。HandlerMapping
会把这个方法连同其所属的Controller实例,封装成一个HandlerExecutionChain
对象(里面还包含了拦截器链),然后返回给DispatcherServlet
。 - 总结 :
HandlerMapping
负责从"请求"到"处理器"的映射 ,它告诉DispatcherServlet
:"这个请求,应该由A座的张三(某个Controller)来处理。"
2. HandlerAdapter
:处理器的"万能执行器"
- 为什么需要它?------ 适配的艺术
- Spring MVC是一个非常灵活的框架,它不仅仅支持我们现在最常用的、基于
@Controller
注解的处理器。在它的发展历史中,还支持过其他类型的处理器,比如需要实现Controller
接口的、或者实现HttpRequestHandler
接口的古老处理器。 - 问题来了 :这些不同类型的处理器,它们的调用方式是完全不一样 的。
DispatcherServlet
总不能写一大堆if-else
来判断:"如果你是@Controller
,我就这样调你;如果你是Controller
接口,我就那样调你......"。这会违反"开闭原则",代码会变得极其臃肿和难以扩展。
- Spring MVC是一个非常灵活的框架,它不仅仅支持我们现在最常用的、基于
HandlerAdapter
如何解决?HandlerAdapter
(处理器适配器)就是为了解决这个问题而生的。它运用了经典的适配器设计模式。- Spring为每一种类型的处理器都提供了一个对应的
HandlerAdapter
实现。比如,RequestMappingHandlerAdapter
专门负责调用被@RequestMapping
注解的方法。 DispatcherServlet
在拿到了HandlerMapping
找到的Handler之后,它不会自己去调用 。它会遍历容器中所有的HandlerAdapter
,并询问:"你们谁能处理这种类型的Handler?"- 那个匹配的
HandlerAdapter
(比如RequestMappingHandlerAdapter
)就会站出来说:"我能!"
- 它的核心职责 :"执行" 。它知道如何以正确的方式 去调用特定类型的处理器,包括如何进行参数解析、数据绑定、调用方法、处理返回值等。最终,它会执行完处理器逻辑,并返回一个统一的
ModelAndView
对象给DispatcherServlet
。
工作流程总结
- 一个请求来了,
DispatcherServlet
(插座板)接收。 DispatcherServlet
问所有的HandlerMapping
(寻路指示灯):"谁知道这个请求该去哪?"- 一个
HandlerMapping
亮了,说:"我知道,应该去找那个三脚插头的电器(某个Controller方法)。" DispatcherServlet
拿到了这个"三脚插头的电器",但它自己不知道怎么供电。于是它问所有的HandlerAdapter
(万能转换头):"你们谁支持三脚插头?"- 一个支持三脚插头的
HandlerAdapter
站出来说:"我来!" - 这个
HandlerAdapter
负责将电流(请求)正确地输送给这个电器,让它工作起来,并把工作成果(ModelAndView
)返回给DispatcherServlet
。
通过HandlerMapping
和HandlerAdapter
这一对精巧的组合,Spring MVC实现了极高的可扩展性 。未来即使出现一种全新的处理器类型,我们只需要为其提供一个新的HandlerMapping
和HandlerAdapter
实现,就可以无缝地集成到框架中,而无需修改DispatcherServlet
的任何代码。
参考小林coding和JavaGuide