springMVC

从源码的角度来看SpringMVC

./mvnw clean install -DskipTests -Pfast
SpringMVC 核心流程
先上图:SpringMVC核心流程图
在这里插入图片描述

简单总结:
首先请求进入DispatcherServlet 由DispatcherServlet 从HandlerMappings中提取对应的Handler
此时只是获取到了对应的Handle,然后得去寻找对应的适配器,即:HandlerAdapter
拿到对应HandlerAdapter时,这时候开始调用对应的Handler处理业务逻辑了(这时候实际上已经执行完了我们的
Controller) 执行完成之后返回一个ModeAndView
这时候交给我们的ViewResolver通过视图名称查找出对应的视图然后返回
最后 渲染视图 返回渲染后的视图 -->响应请求

SpringMVC 源码解析

静态块初始化 DispacterServlet.java

try {
	ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH,
	DispatcherServlet.class);
	defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}

tomcat初始化servlet的init方法
调用 org.springframework.web.servlet.HttpServletBean#initServletBean(servlet 的init)
调用org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
调用org.springframework.web.servlet.FrameworkServlet#onRefresh
调用org.springframework.web.servlet.DispatcherServlet#initStrategies

protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

initHandlerMappings
首先从bean工厂当中得到handlerMapping继而从配置文件中去获取,配置文件一开始便被初始化了,从结果可以
知道我们可以获取到两个handlerMapping,然后赋值给了private List handlerMappings;
接着再初始化adapter,道理同上结果如下图
在这里插入图片描述
接着再初始化adapter,道理同上结果如下图
在这里插入图片描述

org.apache.catalina.core.StandardWrapper#instanceInitialized=true
org.springframework.web.servlet.DispatcherServlet#doDispatch

通过循环HandlerMapping来依次获取HandlerExecutionChain,因为spring当中存在的controller有多重形式,我
们需要处理controller的需要通过HandlerExecutionChain来反射执行controller当中的方法,所以不同的
controller需要new不同的HandlerExecutionChain,那么问题来了HandlerExecutionChain不知道你的
controller是什么类型,但是HandlerMapping知道,所以HandlerExecutionChain的实例化必须依赖
HandlerMapping

org.springframework.web.servlet.DispatcherServlet#getHandler
获取适配器?前面说过不同的controller会获取到不同的handler,那么不同的handler他是怎么实现处理不同的
controller类型呢?spring的做法比较复杂,没有从代码去解决,而是使用了适配器,故而这里根据不同的handler
或得到不同的适配器从而来处理其实就是反射调用controller当中的方法
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

拦截器处理

org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
org.springframework.web.servlet.HandlerAdapter#handle
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handleInternal
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandler
Method
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHand
le
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

反射调用controller的方法
org.springframework.web.method.support.InvocableHandlerMethod#doInvoke
handleReturnValue
writeWithMessageConverters
首先我们查看继承关系(关键查看蓝色箭头路线) 会发现DispatcherServlet无非就是一个HttpServlet
在这里插入图片描述

由此,我们可以去查看Servlet的关键方法:service,doGet,doPost
service:

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
	processRequest(request, response);
	}
	else {
	super.service(request, response);
	}
}

doGet:

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
	processRequest(request, response);
}

doPost:

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	processRequest(request, response);
}

这里会发现 无论是哪个方法 最后都调用了 processRequest(request, response);
我们把焦点放在这个方法上,会发现一个核心的方法:
doService(request, response);
然后会发现 这个方法貌似,呃…有点不一样:

protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;

它居然是一个抽象方法…这就得回到刚刚的继承关系中,找到他的子类了:DispatcherServlet
反正我看到这个方法的实现的时候,脑海里就浮现出4个字:花 里 胡 哨 。代码太多,就不放在笔记里面了,太占
地方了… 为什么这样说呢。。 因为你看完之后会发现 关键在于:

doDispatch(request, response);

是的,没看错,这一行才是关键!
我们把视角切入这个方法 (至于代码…还是不放进来了… ) 总结一下:
把要用的变量都定义好:比如我们的ModelAndView以及异常…等等;
下面即将看到的是一个熟悉的陌生人(噢不,关键词)

processedRequest = checkMultipart(request);

“Multipart” 这个关键词好像在哪见过??…让我想想…(渐渐步入了知识盲区) 哦对!在文件上传的时候!(勉强
想起来了。。) 是的 其实这行代码就是判断当前请求是否是一个二进制请求(有没有带文件) 当然 这里只是提一
下,并不是本文的核心内容。。。(有时间的小伙伴可以自己去了解一下)
好的,现在回到我们的主题,来看看这个方法:

mappedHandler = getHandler(processedRequest);

看过我们上面流程图的同学应该会知道他现在在干嘛。 现在来获取我们的Handler了…从哪获取呢? 从他的
HandlerMapping里面获取。
问题1:至于这个HandlerMappings 哪里来的呢
这个等下讨论 我们先来看看他获取的代码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception
{
	if (this.handlerMappings != null) {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
			logger.trace(
			"Testing handler map [" + hm + "] in DispatcherServlet with name '" +
			getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
			return handler;
			}
		}
	}
	return null;
}

我们能看见他是在遍历一个handlerMappings
问题2:至于这个handlerMapping是什么呢,
请看:
在这里插入图片描述

是2个我们不认识的东西,至于是什么,现在说了也不知道,我们继续往下走
可以看见图片上1188行代码, 他从这个mapping 里面获取了一个handler 如果获取到了 这个方法就走完了, 不
然就下一次循环.
问题1解释:
我们先回到刚刚那个问题,这个HandlerMapping 哪里来的呢。 不多说,上图:
在这里插入图片描述
我们在源码包的DispatcherServlet.properties文件下会看见, 他定义了图片里的这些属性。 重点放在方框内,第
一个属性,就是我们刚看见的HandlerMappings, 也就是说 HandlerMappings也就是他自己事先定义好的呢。至
于第二个属性,咱们待会儿见~
也就是说SpringMVC自己自带了2个HandlerMapping 来供我们选择 至于 为什么要有2个呢? 这时候得启动项目从
断点的角度来看看了;
我们用2种方式来注册Controller 分别是:
作为Bean的形式:

@Component("/test")
public class TesrController implements org.springframework.web.servlet.mvc.Controller{
	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("1");
		return null;
	}
}

以Annotation形式:

@Controller
public class AnnotationController {
	@RequestMapping("/test2")
	public Object test(){
		System.out.println("test");
		return null;
	}
}

我们先用Bean的方式来跑:
视角走到我们的mappedHandler = getHandler(processedRequest);里面
在这里插入图片描述
问题2解释:
来,跟着箭头走,我们发现 我们以Bean的形式注册的Controller 可以从这个BeanNameUrlHandlerMapping里面
获取到对应的Handler ; 这里 我们是不是对于这个HandlerMapping有了懵懂的了解了?
猜想1:
我们来猜一下 如果是以Annotation的形式注册的Controller的话 就会被第二个HandlerMapping获取到。 至于对
不对 这个问题我们先留着。
我们先让代码继续走,会发现 Handler返回出来紧接着会执行下面这个方法,这个方法我们从流程图中可以了解
到,就是在找一个适配器。

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

问题3:何为适配器?
我们先来看看他这个方法里面干了啥:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
			logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
			return ha;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +"]: 
	The DispatcherServlet configuration needs to include a HandlerAdapter that
	supports this handler");
}
其实能看见他是从一个handlerAdapters属性里面遍历了我们的适配器 这个handlerAdapters哪来的呢? 跟我们的
HandlerMappings一样 在他的配置文件里面有写,就是我们刚刚所说的 待会儿见的那个东西~ 不多说 上图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210223131937282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxOTcyMzU4,size_16,color_FFFFFF,t_70)![在这里插入图片描述](https://img-blog.csdnimg.cn/20210223131946631.png)

**问题3解释:**
至于什么是适配器,我们结合Handler来讲, 就如我们在最开始的总结时所说的, 一开始只是找到了Handler 现在
要执行了,但是有个问题,Handler不止一个, 自然而然对应的执行方式就不同了, 这时候适配器的概念就出来
了:对应不同的Handler的执行方案。
当找到合适的适配器的时候, 基本上就已经收尾了,因为后面在做了一些判断之后(判断请求类型之类的),就开
始执行了你的Handler了,上代码:

```java
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这个mv就是我们的ModlAndView 其实执行完这一行 我们的Controller的逻辑已经执行完了, 剩下的就是寻找视
图 渲染图的事情了…
我们这里只是使用了Bean的形式执行了一遍流程 假设使用Annotation呢?

SpringMVC BeanName方式和Annotation方式注册Controller源码分析

现在我们来使用Annotation来注册Controller看看
我们这里 只看不同的地方。
猜想1证明:
首先 在这个HandlerMappings这里 之前的那个就不行了 这里采用了另外一个HandlerMapping 其实也就证明了我
们的猜想1
在这里插入图片描述
然后就是到了我们的适配器了:
在这里插入图片描述

这里我们会看到用的是这个适配器 而我们的Bean方式注册的Controller 的话 使用的是另外两个适配器来的,至于
有什么区别呢? 我们来看看他执行的时候:

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}

我们的Annotation的形式 是拿到这个handler作为一个HandlerMethod 也就是一个方法对象来执行 这时候我们看
看Bean是什么样子的:

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
	return ((Controller) handler).handleRequest(request, response);
}

由最开始可以看到 我们如果以Bean的形式注册Controller的话 我们的实现一个Controller的接口 在这里 他把我们
的handler强制转换为一个Controller来执行了。
总结
其实我们的SpringMVC关键的概念就在于Handler(处理器) 和Adapter(适配器)
通过一个关键的HandlerMappings 找到合适处理你的Controller的Handler 然后再通过HandlerAdapters找到一个
合适的HandlerAdapter 来执行Handler即Controller里面的逻辑。 最后再返回ModlAndView…

requset-----servlet-------方法调用-------controller(java) 类名+方法名-----------indexController—index.所以是通过反射来的.
forward(转发只能转发到servlet或者jsp),所以这里一定是方法调用.
因为你想如果是forward的话,他还是转发到servlet或者jsp,并没有直接转发到controller嘛.如果是转发到servlet,还是通过servlet的方法调用然后调用到controller的.

一开是初始化的时候会把当前请求的路径和类放到一个map中去.那么是如何放的呢?
map1---------------------------------------BeanNameUrlHandlerMapping这个接口来描述的.--------xxxHandlerMapping.
Map<luban,indexControllerTow>-------------map1
第一种方式:
如果页面上面传入的/luban那么就会在这个map中那取出来key为luban的value,然后把这个类通过反射调用Controller的方法.因为是继承了Controller那么就只有一个handlerRequset.那么就直接调用这个方法
第二种方式:
直接加上@Controller注解.
那么就会有一个map2存放值.但是和第一种方式有区别.这里存放的形式如下:其中key为url请求路径,value为方法名称.
map2----------------------------------RequestMappingHandlerMapping的子类来表示的.
Map<index,index1()>----------------------map2.
Map<index2,index2()>----------------------map2.

比如当前请求的路径为localhost:9050/index.html
源码的情况是首先通过第一种方式去拿当前index.html请求url在Map1中是否存在(也就是说当前请求的url下的类是否实现了Controller接口),如果Map1中不存在,那么就会在map2中去找,因为map2中存在key为index的值,所以map2中能够拿出这个方法.**(重点)但是这里是方法并不是类,那么就需要通过这个方法然后去找到这个类,然后在进行反射调用这个方法.**然后就会去调用这个方法了.

List handlerMappings这个集合是在什么时候放入下面两个类的呢?

  • BeanNameUrlHandlerMapping
  • RequestMappingHandlerMapping
    org/springframework/web/servlet/DispatcherServlet.java:283
    静态块中的DispatcherServlet.properties这个文件中.所有都给拿出来.

当springBoot中是/html的时候应该如何处理的呢?
下面都是springMVC的HandlerMapping,都不符合springBoot的.但是springBoot自己写了一个handlerMapping.适应html的.
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
org.springframework.web.servlet.function.support.RouterFunctionMapping

总结:
1.springMVC的执行流程
2.springBoot的两个Map的初始化过程.
3.springBoot对于index.html的请求是如何写到页面中
4.springMVC对于实现了Controller类的Compent中的tiger是如何初始化的和springboot的Map的初始化的区别.以及是如何循环写到页面的.
5.springBoot对于index.html请求是的类型反射拿到的.然后匹配的到Handler的.
6.SpringMVC的参数绑定原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值