springmvc源码学习
SPI机制
传统的springmvc项目,需要我们指定web.xml等配置文件,但是,在spring的官网,官方推荐的并不是xml格式的,而是
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Load Spring web application configuration
//初始化springweb容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(SpringbootAppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
//注册DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("*.do");
}
}
这七行代码,和我们传统的springmvc项目中加web.xml是一样的效果,这个方法是完成servlet容器的初始化,那为什么spring自己写的类,Tomcat在启动的时候,会调用呢?
在Servlet3.0新加的SPI机制是这样要求的:
- 如果一个项目(Tomcat)在启动的时候,需求调用外部系统(spring)的部分方法,完成初始化,那么,在外部系统中,在项目的resource文件下 META-INF/services/javax.servlet.ServletContainerInitializer 声明这么一个文件(路径是不允许变的);文件中声明的类如果实现了ServletContainerInitializer接口;那么,Tomcat在启动的时候 必须要调用文件中声明的类的onStart方法;前提是:必须是一个web项目,否则Tomcat不会调用
- 但是spring官方文档指明开发springmvc,需要实现的是 WebApplicationInitializer 接口,那么WebApplicationInitializer和ServletContainerInitializer之间有什么关系?servlet3.0还有一个规范,如果在 实现了ServletContainerInitializer接口的类 上加上 @HandlesTypes(WebApplicationInitializer.class) 注解,那么onStart方法需要调用注解中接口的所有实现类对应的onStart方法
springmvc应用

这张原理图是在网上随便找了一张
运行原理
- 前台发送请求,请求会首先通过DispatcherServlet,前端控制器
- 前端控制器收到请求,会调用HandlerMapping(处理器映射器)来匹配有没有相对于的handlerMapping,如果有匹配的,会包装成handlerExecutionChain
- 接着dispatcherServlet会调用handlerAdapter,通过handlerAdapter来执行真正的业务逻辑代码,也就是所谓的controller中的方法
- 调用完成之后,返回modelAndView,前端控制器会调用师徒解析器ViewResolver对modelAndView进行解析
- 视图解析器会解析出对应的view,前端控制器根据view并进行视图的渲染(数据填充),然后返回给前端调用者
springmvc核心组件
- 前端控制器 DispatcherServlet
- 处理器映射器 HandlerMapping
- 处理器适配器 HandlerAdapter
- 视图解析器 viewResolver
- ModelAndView
controller的三种配置方式
- @Controller注解
- 实现Controller接口,这种方式,需要在类名增加@Component("/映射地址")
- 实现HttpRequestHandler接口,在类上加@Component("/映射地址")
后面两种原理是一样的,下面会说到;spring默认的handlerMapping有两种:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping;对于@Controller注解的controller,都是由前者来处理的,实现controller接口或者httpRequestHandler接口,是由后者来处理的
spring自带的handlerMapping
- RequestMappingHanderlMapping:@Controller是由该handlerMapping处理的
- BeanNameUrlHandlerMapping:后面两种实现方式都是=该handlerMapping处理的
spring自带的handlerAdapter
- RequestMappingHandlerAdapter:@Controller是由该handlerAdapter处理的
- HttpRequestHandlerAdapter:后面两种实现方式都是该handlerAdapter处理的
- SimpleControllerHadnlerAdapter
springmvc源码
springmvc的源码,我们暂时分为两部分,一是启动初始化,二是调用过程
简单而言,在请求接口的时候,需要根据与URL地址找到对应的处理方法,这个映射关系是在初始化的时候,存入到了一个map集合中
启动初始化
在spring初始化的时候,我们需要关注两个bean的初始化:
RequestMappingHandlerMapping和BeanNameUrlHandlerMapping
RequestMappingHandlerMapping是在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration这里,在springboot自动注入的时候,给注入到beanDefinitionMap中了
BeanNameHandlerMapping 是在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport中注入的
RequestMappingHandlerMapping

由于该mapping实现了InitializingBean,所以,在实例化该bean的时候,会调用父类的afterProperties()方法,在父类的方法中,又会调用到子类的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods这个方法,来初始化URL和method的对应关系;具体的调用逻辑在截图中,这几个方法中,只是有一些简单的校验,所以就跳过

/**
* @param handler
* 在这里其实是根据bean,获取到bean中所有添加了@RequestMapping注解的method,
* 然后把method和url进行映射,并把映射关系存到map中
*/
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {
//userType是当前的类名
Class<?> userType = ClassUtils.getUserClass(handlerType);
//根据类名获取到所有的方法
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//这里是来注册映射关系的
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
在注册映射关系的时候,其实就是将url和对应的方法,存入到了一个map集合中,这里的registerHandlerMethod内部调用到了org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register,这这个方法中,将映射关系存入到了urlLookup这个map中
BeanNameUrlHandlerMapping

由于beanNameUrlHandlerMapping间接的实现了applicationContextAware,所以,在调用org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization的时候,会调用org.springframework.context.support.ApplicationObjectSupport#setApplicationContext;在底层,会调用到org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers

在该方法中,
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.遍历beanName
for (String beanName : beanNames) {
//判断beanName是否是以 / 开头的
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#determineUrlsForHandler
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
推断出来beanName是以 / 开头的话,就会将当前url和对应的beanName添加到 一个map集合中:org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#handlerMap
总结来说
RequestMappingHandlerMapping(@Controller注解)实现了InitializingBean接口
1.在初始化这个bean的时候,会调用afterPropertiesSet(initialization初始化方法)方法,
2.在这个方法中,会获取到当前单实例池中所有的Object类型的bean,过滤掉以scopedTarget.开头的bean
3.获取到bean中定义的方法(得到method名称和映射地址),遍历methods,调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register方法,在这里,会把当前 BeanNameUrlHandlerMapping(Controller接口)是实现了ApplicationContextAware接口的类(中间有多重继承实现)
1.在初始化这个bean的时候,会调用setApplicationContext()方法
2.最终会调用到org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#detectHandlers方法,
3.在这个方法中,会获取到单实例池中所有的Object.class类型的beanName,获取到beanName之后,有一个判断,判断beanName是否是以 / 开头的;如果是,就调用registerHandler(urls, beanName);
4.然后会把当前映射路径和对应的Controller添加到handlerMap中
调用
在第一次调用controller的时候,会对dispatcherServlet进行初始化
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
在request请求过来的时候,会进入到org.springframework.web.servlet.DispatcherServlet#doDispatch,这里是springmvc处理请求的核心方法
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.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
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.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
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 {
}
}
在getHandler()方法中,会根据当前请求的url地址来进行映射方法的查找,如果找到,返回对应的handlerMapping,然后包装成HandlerExecutionChain返回;
在getHandlerAdapter()方法中,根据当前handlerMapping对应的handlerAdapter,找到对应的handlerAdapter,
在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());中会根据方法的入参和实际请求中的参数名对参数进行赋值,然后调用对应的controller方法
遗留问题
1.url中的参数如何解析? XXX/{id}
2.controller入参,dispatcherServlet是如何判断处理的
springmvc源码学习的更多相关文章
- SpringMVC源码学习之request处理流程
目的:为看源码提供调用地图,最长调用逻辑深度为8层,反正我是springMVC源码学习地址看了两周才理出来的. 建议看完后还比较晕的,参照这个简单的模型深入底层,仿SpringMVC自己写框架,再来理 ...
- springMVC源码学习地址
springmvc工作原理以及源码分析(基于spring3.1.0) 感谢作者 宏愿, 在此记录下,以便学习 SpringMVC源码分析(1):分析DispatcherServlet.doDispa ...
- springMvc源码学习之:spirngMvc的参数注入的问题
转载:https://my.oschina.net/lichhao/blog/172562 概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成 ...
- springMVC源码学习之addFlashAttribute源码分析
本文主要从falshMap初始化,存,取,消毁来进行源码分析,springmvc版本4.3.18.关于使用及验证请参考另一篇jsp取addFlashAttribute值深入理解即springMVC发r ...
- SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析
目录 一.前言 二.初始化 1. 容器初始化 根容器查找的方法 容器创建的方法 加载配置文件信息 2. MVC的初始化 文件上传解析器 区域信息解析器 handler映射信息解析 3. Handler ...
- springMvc源码学习之:spring源码总结
转载自:http://www.cnblogs.com/davidwang456/p/4213652.html spring beans下面有如下源文件包: org.springframework.be ...
- springMVC源码学习之获取参数名
1.入口到参数处理调用流程 入口为spring-webmvc-4.3.18.RELEASE.jar中org.springframework.web.servlet.DispatcherServlet. ...
- springMvc源码学习之:spirngMVC获取请求参数的方法2
@RequestParam,你一定见过:@PathVariable,你肯定也知道:@QueryParam,你怎么会不晓得?!还有你熟悉的他 (@CookieValue)!她(@ModelAndView ...
- springMvc源码学习之:spirngMvc的拦截器使用
SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...
随机推荐
- d3.js 地铁轨道交通项目实战
上一章说了如何制作一个线路图,当然上一章是手写的JSON数据,当然手写的json数据有非常多的好处,例如可以应对客户的各种BT需求,但是大多数情况下我们都是使用地铁公司现成的JSON文件,话不多说我们 ...
- PostGIS 递归方法
在Oracle数据库中,有可以实现递归的函数 select * from table_name start with [condition1] connect by [condition2] 最近发现 ...
- 2019-9-9:渗透测试,docker下载dvwa,使用报错型sql注入dvwa
docker下载dvwa镜像,报错型注入dvwa,low级 一,安装并配置docker 1,更新源,apt-get update && apt-get upgrade &&am ...
- day 33 线程锁
Python的GIL锁 - Python内置的一个全局解释器锁,锁的作用就是保证同一时刻一个进程中只有一个线程可以被cpu调度. 为什么有这把GIL锁? 答:Python语言的创始人在开发这门语言时, ...
- JS中的同步异步编程
首先我们先看看同步与异步的定义,及浏览器的执行机制,方便我们更好地理解同步异步编程. 浏览器是多线程的,JS是单线程的(浏览器只分配一个线程来执行JS) 进程大线程小:一个进程中包含多个线程,例如 ...
- Elasticsearch系列---增量更新原理及优势
概要 本篇主要介绍增量更新(partial update,也叫局部更新)的核心原理,介绍6.3.1版本的Elasticsearch脚本使用实例和增量更新的优势. 增量更新过程与原理 简单回顾 前文我们 ...
- day20191012笔记
课程默写笔记: 1.程序架构 C/S 客户端/服务器端 B/S 浏览器/服务器端 2.Tomcat应用服务器 tomcat默认端口号是80:tomcat配置文件中通常端口的定义是8080: 3.使用开 ...
- typedef & #defiine & struct
#define(宏定义)只是简单的字符串代换(原地扩展),它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了. typedef是为了增加可读性而为标识符另起的新名称(仅仅只是个别名), ...
- jsp html 实现隐藏输入框,点击可以取消隐藏&&弹出输入框
jsp代码: <script language="javascript" type="text/javascript"> function chg ...
- VS #region
1.C# 预处理指令 #region使您得以在使用Visual Studio代码编辑器的大纲显示功能时指定可展开或折叠的代码块. #region name 其中:name 希 ...