Spring MVC源码解析

Spring MVC的使用原理其实是通过配置一个Servlet来接管所有的请求,所有的请求由这个Servlet来进行分发处理。

我们可以从web.xml里面看出这一点

<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>

这个Servlet就是Spring MVC提供的DispatcherServlet,它的继承图如下:

DispatcherServlet大致接收请求的时序图如下:

下面我们先设定几个问题然后再去看源码。

  1. DispatcherServlet初始化的时机?

我们将断点打在DispatcherServlet中的onRefresh方法上

,然后启动项目。

从这个堆栈的图可以看出,最开始是HttpServletBean的init方法执行,然后启动Spring容器,Spring容器初始化的最后一步finishRefresh里面进行了事件通知。

FrameworkServlet里有个内部类ContextRefreshListener,实现了ApplicationListener接口,接收到了上面的事件通知,执行onApplicationEvent方法。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}

继续跟踪onApplicationEvent方法

public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
synchronized (this.onRefreshMonitor) {
onRefresh(event.getApplicationContext());
}
}

发现最终是由onRefresh方法来进行具体处理的,并且这个onRefresh方法在FrameworkServlet里面是个空方法,是由它的子类DispatcherServlet来实现的。

我们来看DispatcherServlet里的onRefresh方法

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

这样就把SpringMVC的九大组件给初始化了。

  1. 我们的@RequestMapping注解在方法上,SpringMVC是怎么根据这个注解就能将对应的请求执行到这个方法上的?

从上面可以看到有个initHandlerMappings方法:

private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; if (this.detectAllHandlerMappings) {
// 找所有实现了HandlerMapping接口的类
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");
}
}
}

继续跟踪getDefaultStrategies方法

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
//省略部分代码...
}
return strategies;
}
else {
return new LinkedList<>();
}
}

可以看出对classNames进行了遍历,这里有两个值,

BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,我们一般用的是RequestMappingHandlerMapping。

RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,我们来看看它实现的afterPropertiesSet方法。

public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet();
}

前面都是一些配置,后面是调用父类的afterPropertiesSet方法,此方法里只有initHandlerMethods一个方法

protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}

主要看processCandidateBean方法

protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}

继续跟踪detectHandlerMethods方法

protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//利用工具,取出类里面的方法并组装成map
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.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
//遍历map,处理里面的方法
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

继续跟踪里面的register方法

public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
} String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
} CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
} this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

可以看出mappingLookup、urlLookup、registry都放入了值。这时我也不知道每个的具体作用。我把断点打到这3个属性上。然后前端发起一个get请求。

  1. 请求匹配的过程?

前端发起请求,断点停在了AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl方法上,以前端发起的请求路径,从urlLookup上获取对应的值。

最后看看请求分发的主流程,也是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 {
//multipart请求处理
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // 获取合适的HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
} //获取合适的HandlerAdapter
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;
} // handle执行调用(核心)
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 {
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);
}
}
}
}

总结:

  1. tomcat的Servlet调起Spring容器启动,Spring容器启动完,事件通知到SpringMVC的DispatcherServlet。

  2. 这时会扫描所有的bean,将注解了@Controller和@RequestMapping的解析出来。

  3. 前端请求发过来,DispatcherServlet接收到(因为它是个servlet,配置在web.xml的),根据上一步处理好的映射关系,找到对应的方法来处理。

如通过/test能找到test方法

@RequestMapping("/test")
public String test(String name, HttpServletRequest request,Model model){
System.out.println("name");
model.addAttribute("date",new Date());
return "success";
}
  1. 找到对应的方法后,反射调用
method.invoke(Object obj,Object... args)
  1. 组装modelAndView渲染视图到前端

深入了解SpringMVC源码解析的更多相关文章

  1. SpringMVC源码解析- HandlerAdapter - ModelFactory(转)

    ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...

  2. springMVC源码解析--ViewResolver视图解析器执行(三)

    之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...

  3. SpringMVC源码解析- HandlerAdapter - ModelFactory

    ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...

  4. springMVC源码解析--ViewResolverComposite视图解析器集合(二)

    上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...

  5. springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)

    上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...

  6. springmvc源码解析MvcNamespaceHandler之<mvc:view-resolvers>

    说在前面 本次主要介绍springmvc配置解析. springmvc配置解析 本次介绍MvcNamespaceHandler. 进入到这个方法org.springframework.web.serv ...

  7. SpringMVC源码解析

    一:springmvc运行过程: 1. dispatcherServlet 通过 HandlerMapping 找到controller2. controller经过后台逻辑处理得到结果集modela ...

  8. SpringMVC源码解析- HandlerAdapter初始化

    HandlerAdapter初始化时,主要是进行注解解析器初始化注册;返回值处理类初始化;全局注解@ControllerAdvice内容读取并缓存. 目录: 注解解析器初始化注册:@ModelAttr ...

  9. springmvc源码解析-初始化

    1.      概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根 ...

随机推荐

  1. mysql update语句的执行流程是怎样的

    update更新语句流程是怎么样的 update更新语句基本流程也会查询select流程一样,都会走一遍. update涉及更新数据,会对行加dml写锁,这个DML读锁是互斥的.其他dml写锁需要等待 ...

  2. PTA习题6-8 统计一行文本的单词个数 (15分)

    参考<c和指针>里面运用strtok函数打印空白标记符(如\n,\t)的程序改写而成的代码 在之前我自己写了一个60行的链表版本的统计程序 相比之下这个strtok函数的程序要简洁明了的多 ...

  3. 蝉知CMS 7.X XSS漏洞复现

    个人博客地址:xzajyjs.cn 作为一个开源的企业门户系统(EPS), 企业可以非常方便地搭建一个专业的企业营销网站,进行宣传,开展业务,服务客户.蝉知系统内置了文章.产品.论坛.评论.会员.博客 ...

  4. Java并行任务框架Fork/Join

    Fork/Join是什么? Fork意思是分叉,Join为合并.Fork/Join是一个将任务分割并行运行,然后将最终结果合并成为大任务的结果的框架,父任务可以分割成若干个子任务,子任务可以继续分割, ...

  5. SingnalR 从开发到生产部署闭坑指南

    前天倒腾了一份[SignalR在react/go技术栈的实践], 步骤和思路大部分是外围框架的应用, 今天趁热打铁, 给一个我总结的SignalR避坑指南. 1.SignalR 默认协商 不管是.NE ...

  6. 【UE4 C++】Tick的三种方式、异步蓝图节点

    Tick的三种方式 包括 默认 Tick (Actor.Component.UMG) TimerManager 定时器 FTickableGameObject 可以写原生 Object 也可以继承UO ...

  7. 【二食堂】Alpha - Scrum Meeting 3

    Scrum Meeting 3 例会时间:4.13 12:00 - 12:30 进度情况 组员 昨日进度 今日任务 李健 1. 继续学习前端知识,寻找一些可用的框架.issue 1. 搭建主页html ...

  8. FastAPI 学习之路(三十三)操作数据库

    通过创建pydantic模型进行验证提交数据 from pydantic import BaseModel class UserBase(BaseModel): email: str class Us ...

  9. vue混入mixin的使用,保证你看的明明白白!

    场景描述 有些时候,我们发现有些组件部分功能代码是几乎是一样的. 这个时候,我们就可以将相同的逻辑代码抽离出来 此时我们的主角混入mixin就登场了 下面我们有a-test和b-test两个组件,点击 ...

  10. 算法:九宫格问题--奇数阶魔方(Magic-Square)

    一.魔方介绍 魔方(这里是简称,也可以叫幻方.魔术矩阵,Magic Square)是 n×n 正方形网格(n 为每侧的单元数),里面每个单元格填充了不同的正整数 1, 2, 3, ... , n2,并 ...