• 151572

    文章

  • 1250

    评论

  • 10

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

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

上一篇: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;
	}

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

 


695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

自定义皮肤 主体内容背景
打开支付宝扫码付款购买视频教程
遇到问题联系客服QQ:419400980
注册梁钟霖个人博客