文档章节

SpringMVC源码分析-DispatcherServlet-init方法分析

特拉仔
 特拉仔
发布于 2019/12/15 18:34
字数 2668
阅读 29
收藏 1

上一篇:SpringMVC源码分析-DispatcherServlet实例化干了些什么

先吐槽一下。。。写了两小时的博客突然被俺家小屁孩按了刷新,东西不见了,建议OSCHINA能够自动定时保存啊。让我先安静一下。。。。

因为前面在阅读Tomcat源码的时候时序图中没有画调用Servlet init方法的步骤,在这里补充一下文字说明。 Tomcat调用顺序:

StandardWrapperValve.invoke()
StandardWrapper.allocate()
StandardWrapper.loadServlet()
DispatcherServet(实际是父类的).init()

通过上面的调用过程最终到达了DispatcherServlet的init(),由Spring的DefaultListableBeanFactory去完成DispatcherServlet.properties中配置类的初始化工作

时序图

说明

图中最核心的方法就是DispatcherServlet.initStrategies(),见源代码

	protected void initStrategies(ApplicationContext context) {
		/**
		 * 这里的每一个方法都非常的重要,他们都遵循一个逻辑:如果在容器中没有拿到对应类型的对象,则使用DispatcherServlet.properties当中配置
		 * 的值,使用Spring容器创建一个新的Bean(注意是prototype,所以在singletonObjebs中无法找到它们。。。为什么是prototype喃,说实话我也不
		 * 是很确定,可能因为Servlet可能会有多个吧。。。但想想又觉得很牵强因为DispatcherServlet只有一个对象,而且又是在init方法初始化的,
		 * init方法只会被调用一次,无论处理多少请求,它们都会是线程共享的啊。。。也许Spring并不想在singletonObjects当中存放它才设置为prototype
		 * 的吧
		 *
		 * 因为是用Spring进行实例化的,所以可以使用Spring提供的扩展点干预这些默认的类的行为,比如可以将protyotype设置为singleton,可以修改它们
		 * 一些属性的默认值等等
		 */
		//文件上传相关
		initMultipartResolver(context);
		//国际化 先从容器中拿名为localResolve的LocaleResolver.class对象,如果没有则使用默认的AcceptHeaderLocaleResolver(Spring-createBean)
		initLocaleResolver(context);
		//主题样式 先从容器中拿名为themeResolver的ThemeResolver.class对象,如果没有则使用默认的FixedThemeResolver(Spring-createBean)
		initThemeResolver(context);
		/**--------------非常重要-------------
		 * 初始化请求映射规则
		 * 先从容器中拿名为HandlerMapping.class对象列表,如果没有则使用默认的以下三个配置项
		 * RequestMappingHandlerMapping:这是最重要的,是通常我们在Controller当中配置的RequestMapping的映射处理类
		 * BeanNameUrlHandlerMapping:是我们的Controller实现了Controller接口,然后用@Component("beanName"),访问时url:localhost:port/contextpath/beanName
		 * RouterFunctionMapping:通过HttpRequestHandler实现Controller,具体细节不清楚,从来没见过更没用过
		 */
		initHandlerMappings(context);
		/**--------------非常重要-------------
		 * Request\Response的重要处理,比如入参与出参的同样格式化
		 * 先从容器中拿名为HandlerAdapter.class对象列表,如果没有则使用默认的以下四个配置项:
		 * HttpRequestHandlerAdapter:
		 * SimpleControllerHandlerAdapter:
		 * RequestMappingHandlerAdapter:
		 * HandlerFunctionAdapter:
		 */
		initHandlerAdapters(context);
		/**
		 * 异常 先从容器中拿HandlerExceptionResolver.class对象列表,如果没有则使用默认的以下三个配置项
		 * ExceptionHandlerExceptionResolver:
		 * ResponseStatusExceptionResolver:
		 * DefaultHandlerExceptionResolver:
		 */
		initHandlerExceptionResolvers(context);
		//请求到视图的转换 从容器中拿名为viewNameTranslator的RequestToViewNameTranslator,如果没有则使用默认的DefaultRequestToViewNameTranslator
		initRequestToViewNameTranslator(context);
		//视图转换器 从容器中拿ViewResolver对象列表,如果没有则使用默认的InternalResourceViewResolver
		initViewResolvers(context);
		//重定向视图管理器 从容器中拿名为flashMapManager的FlashMapManager,如果没有则使用默认的SessionFlashMapManager
		initFlashMapManager(context);
	}

源码中已经比较详细的写了每个方法的注释,就不再赘述了。

概要说明

这里面的每个方法当然都很重要,但是结合日常开发来分析,initHandlerMappings,initHandlerAdapters,initViewResolvers它们三个是解决请求映射、参数解析-绑定-格式化、视图渲染等功能,所以重点拿initHandlerMappings,initHandlerAdapters方法来分析。这两个方法底层逻辑都是先从Spring中找有么有对应的Bean,如果没有则使用DispatcherServlet.properties中配置的Bean,由Spring完成他们的实例化。由于时间、篇幅等原因就不分析里面的每一个类的实例化和功能,这三种分别拿一个比较典型的类进行分析。

initHandlerMappings

使用RequestMappingHandlerMapping作为典型进行讲解它的实例化,不包含它的处理请求的部分

此类干的重要事情就是收集Controller当中的符合规则的HandlerMethod,在处理请求的时候,使用请求路径和这些HandlerMethod进行匹配,找最优匹配进行处理

虽然我选择了这个类做讲解,但是从哪里入手看喃?既然这个类是被Spring实例化的,那么它肯定实现什么接口口、继承了什么类或者使用了什么Spring的特殊注解,拿出它的类结构图看看

可以看到实现了InitializingBean接口,Spring在实例化对象快结束的时候会调用实现类的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());
		//这里面就是收集Controller的映射关系
		super.afterPropertiesSet();
	}

把断点打到这里一步步的往下走,最终可以找到关键代码,这个过程就省略了,我直接把调用栈贴出来,已经对比较重要方法做注释

RequestMappingHandlerMapping.afterPropertiesSet()
AbstractHandlerMethodMapping.afterPropertiesSet()
AbstractHandlerMethodMapping.initHandlerMethods()
AbstractHandlerMethodMapping.processCandidateBean()
AbstractHandlerMethodMapping.detectHandlerMethods()
RequestMappingHandlerMapping.getMappingForMethod()

AbstractHandlerMethodMapping.initHandlerMethods()

	protected void initHandlerMethods() {
		//循环Spring Context当中所有的Bean,看是否满足被@Controller或者@RequestMapping修饰的条件,如果满足则进行映射关系收集
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		/**
		 * 开启锁,返回一个只可读的请求路径与HandlerMethod的映射Map
		 */
		handlerMethodsInitialized(getHandlerMethods());
	}

AbstractHandlerMethodMapping.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);
			}
		}
		//isHandler:看BeanType是否@Controller或者@RequestMapping修饰,如果满足条件,才进行下面的映射关系收集逻辑
		if (beanType != null && isHandler(beanType)) {
			detectHandlerMethods(beanName);
		}
	}

AbstractHandlerMethodMapping.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);
			/**
			 * 从这里开始有两层调用都用到了lambda表达式,看源码时需要注意,在DEBUG时,会跳回到上一层的lambda表达式对应的代码段执行
			 *
			 * selectMethods里面是在从缓存(创建Bean的时候因为其他功能已经解析了一遍类的Class)中拿类的所有Method,然后
			 * 调用getMappingForMethod()方法将Controller符合条件的method与期望请求路建立关系以备请求时做匹配使用,并将匹配到结果放入methods集合
			 * 当中
			 */
			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));
			}
			/**
			 * 注册HandlerMethod,只有注册之后,在后面才能从registry当中拿到一个只读的Mapping
			 */
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

RequestMappingHandlerMapping.getMappingForMethod()

	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		/**
		 * 从Method上面拿到请求路径
		 */
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			/**
			 * 从Type上面拿到请求路径
			 */
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				/**
				 * 进行组合:比如method上面是"/user/{age}",type上面是"/my",则组合后的结果就是"/my/user/{age}"
				 */
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).build().combine(info);
			}
		}
		return info;
	}

这里有必要把RequestMappingInfo单独解释一下,RequestMappingInfo对应@RequestMapping,里面的属性在init的时候也会根据@RequestMapping的参数值对应设置

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
​
    @Nullable
    private final String name;
    //Patterns对应url,就是RequestMapping注解value中的配置
    private final PatternsRequestCondition patternsCondition;
    //Methods对应 http method,如GET,POST,PUT,DELETE等
    private final RequestMethodsRequestCondition methodsCondition;
    //params对应http request parameter
    private final ParamsRequestCondition paramsCondition;
    //headers 对应http request 的请求头
    private final HeadersRequestCondition headersCondition;
    //consumes对应request的提交内容类型content type,如application/json, text/html
    private final ConsumesRequestCondition consumesCondition;
    //produces指定返回的内容类型的content type,仅当request请求头中的(Accept)类型中包含该指定类型才返回
    private final ProducesRequestCondition producesCondition;
    //如果以上都还不能达到你过滤请求的目的,还可以自定义
    private final RequestConditionHolder customConditionHolder;
    
    //-------------省略很多代码------------------
    /**
     * 请求匹配到多个RequestMapping,则需要排序,选择最优的HandlerMethod进行处理
     */
    public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
        int result;
        // Automatic vs explicit HTTP HEAD mapping
        if (HttpMethod.HEAD.matches(request.getMethod())) {
            result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
            if (result != 0) {
                return result;
            }
        }
        result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
        if (result != 0) {
            return result;
        }
        result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
        if (result != 0) {
            return result;
        }
        result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
        if (result != 0) {
            return result;
        }
        result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
        if (result != 0) {
            return result;
        }
        result = this.producesCondition.compareTo(other.getProducesCondition(), request);
        if (result != 0) {
            return result;
        }
        // Implicit (no method) vs explicit HTTP method mappings
        result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
        if (result != 0) {
            return result;
        }
        result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
        if (result != 0) {
            return result;
        }
        return 0;
    }
​
}

到此Spring已经使用RequestMappingHandlerMapping为使用@Controller和@RequestMapping注解的Controller类收集好了所有的请求映射,等待处理请求。

initHandlerAdapters

使用RequestMappingHandlerAdapter作为典型进行讲解它的实例化,不包含它的处理请求的部分

分析它的套路和RequestMappingHandlerMapping一样,上来就看afterPropertiesSet()方法

	public void afterPropertiesSet() {
		/**
		 * 找到Spring容器中被@ControllerAdvice和@RestControllerAdvice修饰的Bean,并添加到一个List当中
		 * @ControllerAdvice是一个Controller增强器,在项目中曾经被用来做异常统一处理
		 * 这两个注解可以结合@ExceptionHandler, @InitBinder, @ModelAttribute三个注解使用将方法作用到全局上面
		 * 详细使用参考官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
		 */
		initControllerAdviceCache();
		/**
		 * ----------------非常重要----------------------
		 * argumentResolvers:参数解析器
		 * initBinderArgumentResolvers:初始化参数绑定解析器
		 * returnValueHandlers:Controller.method invoke之后返回值解析器
		 *
		 * SpringMVC都默认提供了一大批各种各样的解析器,它们共同组成了SpringMVC的强大功能
		 *
		 */
		if (this.argumentResolvers == null) {
			/**
			 * 获取默认的参数解析器,这些解析器是写死在代码中的
			 */
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
			/**
			 * 取默认的InitBinder参数解析器,这些解析器是写死在代码中的
			 */
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			/**
			 * 取默认的返回值解析器,这些解析器是写死在代码中的
			 */
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

此方法很简单,就是实例化默认的各种解析器,在此之前将@ControllerAdvice修饰的Bean找出来放在一个集合当中

关于@ControllerAdvice可以在官网文档中去出看或者百度

随便找一个Revolver看看它的功能

RequestParamMethodArgumentResolver负责识别@RequestParam的参数,将其中的name和真正请求当中的Key进行匹配

PathVariableMethodArgumentResolver负责识别rest风格中具体位置中的参数值

Spring命名确实也很规范,通过看名称几乎就能知道它是干什么的,所以开发当中命名很重要,不要嫌弃名称太长,写起来太麻烦。

 

© 著作权归作者所有

特拉仔
粉丝 62
博文 266
码字总数 259663
作品 0
渝中
部门经理
私信 提问
加载中

评论(2)

特拉仔
特拉仔 博主
亿图示示的EdrawMax
陈年之后是青葱
陈年之后是青葱
老大,画图工具用什么画的呀
Spring MVC 原理探秘 - 一个请求的旅行过程

1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章。为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一下 Sp...

coolblog.xyz
2018/07/02
0
0
SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

在我们第一次学Servlet编程,学java web的时候,还没有那么多框架。我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转到我们定义好的...

相见欢
2013/01/15
1.9W
21
Spring3.2 MVC 分析

Spring3.2 MVC 分析: SpringMVC现在应该用得很广泛了,其配置清晰,灵活度,定制能力等都是很强的,相比Struts2也是胜过一筹,还是从源码来分析一下,SpringMVC为我们做了什么。 先从配置文...

ihaolin
2014/02/03
3.1K
2
Spring源码分析之WebMVC

作者: 一字马胡 转载标志 【2018-01-07】 更新日志 导入 Spring源码分析系列文章索引: Spring源码分析之Bean的解析 Spring源码分析之Bean的加载 Spring源码分析之AOP 本文是系列文章的第四...

疼丸李白
2018/01/07
0
0
Spring MVC 原理探秘 - 容器的创建过程

1.简介 在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的。Spring MVC 可对外提供服务时,说明其已经处于了就绪状态。再次之前,Spring MVC 需要进行一系列的初始化操作。...

coolblog.xyz
2018/07/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

每天AC系列(六):有效的括号

1 题目 LeetCode第20题,这题比较简单,匹配括号. 2 栈 这是栈的典型应用,括号匹配,当然不需要直接使用栈,使用一个StringBuilder即可: if(s.isEmpty()) return true;char a = s.charAt(0);...

Blueeeeeee
今天
27
0
Spring AOP-06-切入点类型

切入点是匹配连接点的拦截规则。之前使用的是注解@Pointcut,该注解是AspectJ中的。除了这个注解之外,Spring也提供了其他一些切入点类型: • 静态方法切入点StaticMethodMatcherPointcut •...

moon888
昨天
90
0
Class Loaders in Java

1. Introduction to Class Loaders Class loaders are responsible for loading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Also, they are part of the ......

Ciet
昨天
96
0
以Lazada为例,看电商系统架构演进

什么是Lazada? Lazada 2012年成立于新加坡,是东南亚第一电商,2016年阿里投资10亿美金,2017年完成对lazada的收购。 业务模式上Lazada更偏重自营,类似于亚马逊,自建仓储和为商家提供服务...

春哥大魔王的博客
昨天
62
0
【自用】 Flutter Timer 简单用法

dart: void _startTime() async { _timer = Timer(Duration(seconds: sec), () { fun(xxx,yyy,zzz); }); } @override void dispose() { _timer.cancel()......

Tensor丨思悟
昨天
65
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部