热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringWeb源码之MVC主体结构

前言springmvc可以说是大部分小伙伴做web项目的第一个使用到的框架,当然如果比较久远的话大家还会学到structs或structs2,不过有一

前言

spring mvc可以说是大部分小伙伴做web项目的第一个使用到的框架,当然如果比较久远的话大家还会学到structs或structs2,不过有一些原因被淘汰了。之前用SSH也逐步转变成了SSM框架。当然目前到了微服务项目也离不开它,唯一不同的是前后端分离了,基本不需要他的视图。当然小编还是会带过他是视图。
今天为了大家带来spring mvc的整体架构以及核心流程。咱们进入正题


Spring MVC主体架构

在刚刚学习java企业应用级应用的时候,咱们首先学到的是Servlet,那其实spring mvc是基于他来封装开发的,那咱们先来回顾他的流程


Servlet的流程

先看一下下图:
在这里插入图片描述
看了上图,其实流程比较简单一目了然,那web怎么到达Servlet以及Servlet怎么到达Jsp的,他的本质又是什么?这里涉及到一些知识。



  • 当浏览器操作发起get或者post,其中包含了网络的IO

  • Jsp也是一个Servlet,Servlet中的request以及response转发到了Jsp(突然想起jsp的九大内置对象)。

  • Jsp基于目标生成html,然后基于response中的inputstream流返回到浏览器。

当然这个不是重点,接下来我们看一下spring mvc的流程


Spring MVC 流程

在这里插入图片描述
其实和Servlet差不多,但是多出了Controller层,将业务逻辑交给了Controller,之后返回给DispatchServlet再转发给View,然后基于模板生成html再返回给浏览器。
那spring mvc为什么要加上控制层,相较于servlet流程有哪些优点,那咱们继续往下看。


MVC体系结构

传统的映射体系,学习servlet的时候大家应该知道,我们配置一个映射的话,基本就是xx/xx.do,这样映射比较麻烦,第二是传递的参数,参数获取或封装都比较麻烦,第三就是servlet请求如果有多个就需要有多个servlet来处理。还有其他的问题,基于上面的种种问题,那spring mvc进行了优化。那首先我们看一下他的体系结构图:

在这里插入图片描述
优点:



  1. url映射灵活且支持ant表达式。

  2. 参数转换,可以帮我们自动封装,只需声明即可自动匹配。

  3. 控制的话基于注解的话,一个类里面可以写很多方法。

  4. 视图的话更加丰富,还支持el表达式。

  5. 异常的处理以及拦截器可以贯穿很多地方

  6. 静态资源也更加灵活。

看了体系结构,那我们看一下他的核心流程以及细节。这里会涉及他的源码:
在这里插入图片描述



  • 首先dispatchServlet要到Controller,需要根据url来找到对应的controller。

  • 需要一个url与controller之间的映射,那就需要handlerMapping组件,里面应该有个map,里面存在url以及对应的Handler。

  • Handler有我们熟知的requestMapping注释的方法,有servlet、实现controller接口的还有实现httpRequestHandler的,这些都是可以进行映射。

  • 适配器 帮助我们找到对应的Handler

  • 创建视图 这里ViewResolver主要是因为视图可能有多种,则需要对应的视图解析并渲染,同时还有缓存不需要每次都创建一个对象。

  • 渲染视图


代码演示

这里演示的是平常不太用到的

**Servlet**
```java
@Controller("/servlet")
public class ServletHandler extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("hello luban uncle");
}
}
@Component
public class ServletHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Servlet);
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((Servlet) handler).service(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}

值得注意的是Servlet需要加上对应的适配器,因为servlet没有自己的适配得手工加上,如果不加则会报错
javax.servlet.ServletException: javax.servlet.ServletException: No adapter for handler [com.lecture.mvc.ServletHandler@409c7566]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
其他的为什么不用加,想必大家已经明白了,因为spring对大多数已经自动加入了handlerAdapter,不需要手动加进去。
具体是在springwebmvc的jar下面的DispatcherServlet.properties文件。

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

controller

@Component("/hi")
public class ControllerHandler implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
//viewResolver
ModelAndView userView = new ModelAndView("userView");
userView.addObject("name","hello");
return userView;
}
}
**HttpRequestHandler**
```java
public class HttpRequestHandlerTest implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
httpServletResponse.getWriter().write("hello world http request handler");
}
}

看到这儿大家是否觉得有疑问,那为什么没有HandlerMethod的代码示例,这个暂且不表,小编后续会说明。
实际过程中controller和HttpRequestHandler在实际过程中99%是不会用到的,那为什么会有这两个呢,主要是为了动态扩展Handler,这两种bean,直接可以放入spring的ioc容器里面。


接下来小编看一下他的初始化过程。


组件初始化以及源码阅读

上面的HandlerMapping,HandlerAdpater,ViewResolver以及View本质上都是Bean,并被spring Ioc容器所管理,那这些bean怎么被DispatchServlet所使用的呢,其实很简单,他在启动的时候找出上面的Bean并且添加进去,那这样DispatchServlet必须有初始化的动作。下图看DispatchServlet中包含哪些:

在这里插入图片描述

从上图可看出中DispatchServlet组件中包含多个HandlerMapping,HandlerAdpater以及ViewResolver,当然大家是否有疑问怎么没有View了,其实View是动态生成的,然后看一下源码:
org.springframework.web.servlet.DispatcherServlet

protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
//初始化HandlerMappings
this.initHandlerMappings(context);
//初始化HandlerAdapters
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
//初始化HandlerAdapters
this.initViewResolvers(context);
this.initFlashMapManager(context);
}

大家先不需要关心其他的组件,因为不属于核心组件。这里初始化的源码咱们只看一个initHandlerMappings,因为其他的init都相差不大,很简单

initHandlerMappings

private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
//找到容器中所以的HandlerMappings
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
//设置进去
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order. 排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
//都为空则去找配置文件内容,就去找对应的文件
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}

这样组件初始化就完成了。接下来初始化后怎么调用到他。那接着看一下调用的流程以及源码。
首先调用的时序图如下:

在这里插入图片描述
时序图非常简单,那咱们看一下源码:
首先不管是post还是get最终都回到这个org.springframework.web.servlet.DispatcherServlet#doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
.......
try {
//调用到doDispatch方法
doDispatch(request, response);
}
.....
}

doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//找到我们的handler,也就是从handlerMappings中找到我们的映射
//拿到的是HandlerExecutionChain 里面包装了Handler,里面使用HandlerExecutionChain
//主要里面有需要执行的拦截器的方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//找到我们的handler适配器,handlerAdapters里面查找,如果找不到直接报错
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//找到适配器后首先是执行前置拦截方法,看是否被拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//真正调用Handler根据适配器发起调用,返回了modelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//设置viewName
applyDefaultViewName(processedRequest, mv);
//后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//这里是创建视图并渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

创建视图org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName

public View resolveViewName(String viewName, Locale locale) throws Exception {
//不使用缓存直接创建
if (!isCache()) {
return createView(viewName, locale);
}
else {
//双重锁检测
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
//这里因为view是有容量的,这里viewCreationCache使用了LinkedHashMap
//这边满了之后会溢出淘汰最近最少使用LRU
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace(formatKey(cacheKey) + "served from cache");
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}

渲染视图 也就是讲model作为属性填充到request,然后request进行forward转发
org.springframework.web.servlet.view.AbstractView#render

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
//填充模型
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}

看源码的话可能不是那么容易,希望大家调试一遍那就啥都明白了。


总结

小编好久没有写ssm框架,然后又web.xml以及dispatchServlet的配置了,手好生疏,但是这个不是重点,重点是我们能够了解里面对应的代码,以及整体的流程即可。这里虽然有一些即使用了那么多年的spring也没接触到一些内容,比方说HttpRequestHandler ,其实他和@ResponseBody注解相似。
这里主要是回顾spring mvc,让大家知道怎么用以及为什么这么用。包括之前为什么我们要在spring.xml中配置HandlerAdpater。源码阅读还是枯燥的。希望再接再厉,吃透ssm的常用方法及框架。



推荐阅读
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • MVC设计模式的介绍和演化过程
    本文介绍了MVC设计模式的基本概念和原理,以及在实际项目中的演化过程。通过分离视图、模型和控制器,实现了代码的解耦和重用,提高了项目的可维护性和可扩展性。详细讲解了分离视图、分离模型和分离控制器的具体步骤和规则,以及它们在项目中的应用。同时,还介绍了基础模型的封装和控制器的命名规则。该文章适合对MVC设计模式感兴趣的读者阅读和学习。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • SpringBoot简单日志配置
     在生产环境中,只打印error级别的错误,在测试环境中,可以调成debugapplication.properties文件##默认使用logbacklogging.level.r ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 本文介绍了一道网络流题目hdu4888 Redraw Beautiful Drawings的解题思路。题目要求以行和列作为结点建图,并通过最大流算法判断是否有解以及是否唯一。文章详细介绍了建图和算法的过程,并强调在dfs过程中要进行回溯。 ... [详细]
author-avatar
ayo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有