springMVC之HandlerMapping

写在前面

spring源码版本为5.3.1

了解HandlerMapping

HandlerMapping构建了请求与处理器间的映射,当接收到请求后,DispatcherServlet将会根据映射找到具体的处理器去对请求进行处理。之所以称为Handler-Mapping,是因为后端处理器在springMVC中统称为Handler(我们通常打交道的都是Controller,但处理器绝不仅仅是Controller),我们首先了解一下HandlerMapping接口的设计。

public interface HandlerMapping {
	/**
	 * HandlerExecutionChain是由Handler和一些HandlerInterceptor构成,表示执行链
	 */
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

看了这个接口后,我有几点想弄清楚,第一,HandlerInterceptor这个拦截器作用范围在哪里?第二,请求与处理器间的映射关系是何时建立的?带着这两点疑问,我们来阅读源码。

了解HandlerInterceptor
public interface HandlerInterceptor {
	// 找到handler后,调用handler#handle方法前
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}

	// 调用handler#handle方法后
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
	
	// 请求处理完成后的回调,即视图渲染后
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

通过接口可以发现,HanlderInterceptor可以帮助我们在请求过程中,以及请求完成后对请求做出一些处理,这提供了另一种有别于Filter处理请求的思路,这里简单聊一下HanlderInterceptor与Filter之间的区别。

HanlderInterceptor 位与DispatcherServlet之后,指定给HandlerMapping,可以对HandlerMapping所管理的映射关系进行拦截,而且粒度更细,我们可以在Handler执行前,执行后以及整个Dispatcher-Servlet流程完成后添加处理逻辑,同时我们可以为不同的Handler指定不同的HanlderInterceptor来达到灵活配置。

Filter更像是一种宏观调控,应用于web程序中的servlet,它位于DispatcherServlet之前,比Hanlder-Interceptor优先级要高,应用层面Filter对DispatcherServlet进行拦截,而HanlderInterceptor是对Handler。

如果我们使用xml给HandlerMapping配置HanlderInterceptor的话,可以按照下面这种方式进行配置。

<!--我们可以配置多个HandlerMapping,给每个HandlerMapping配置不同的HanlderInterceptor -->
<bean id ="" class="xxx.xxx.xxx.xxxHandlerMapping">
	<property name="interceptors">
		<list>....<list/>
	<property/>
<bean/>

我们也可以使用全局配置,这样就不用给每个HandlerMapping单独再配置了

<mvc:interceptors>
   <mvc:interceptor>
    <!-- 拦截路径 -->
    <mvc:mapping path="/**" />
    <!-- 不必拦截路径 -->
    <mvc:exclude-mapping path="/static/**" />
    <bean class="xxx.xxx.xxxInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

注意,上面这段配置解析后生成的bean并没有注入到当前容器,而是被注入到父容器中,而且注入的bean并不是我们配置的bean,它其实把我们配置的bean包装成MappedInterceptor注入其中。这段代码在InterceptorsBeanDefinitionParser中,大家感兴趣的可以去看一下。

MappedInterceptor是HanlderInterceptor的一个包装类,它使用URL模式来决定是否应用于给定的请求。上面谈到全局配置时将会把MappedInterceptor注入到父容器中,那么它是如何被检测到的呢?

AbstractHandlerMapping:
// 容器初始化的时候将会去父子容器中找MappedInterceptor的bean 
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
			obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
构建请求与Handler映射关系

HandlerMapping的实现类有很多,构建映射关系的策略也有不同,大体上分为两类,分别为请求到处理类,请求到处理方法间的映射。我们从这两个方向去解读源码。

请求到处理类

在这里插入图片描述
之所以将BeanNameUrlHandlerMapping与BeanNameUrlHandlerMapping放在一起来讲,是因为它们都直接或间接继承于AbstractUrlHandlerMapping,它们都是根据url来建立请求与处理类的映射关系。

AbstractUrlHandlerMapping中提供了通用的注册映射关系的代码

	protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
		for (String urlPath : urlPaths) {
			registerHandler(urlPath, beanName);
		}
	}

	protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
		Object resolvedHandler = handler;
		// 获取handler实例
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.getBean(handlerName);
			}
		}
		// 根据url从映射缓存中获取映射的handler实例
		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			// 这里说明请求对应的Handler必须只有一个,如果已经存在,将会报错
			if (mappedHandler != resolvedHandler) {
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}else {
			if (urlPath.equals("/")) {
				setRootHandler(resolvedHandler);
			}else if (urlPath.equals("/*")) {
				// 设置默认的Hanlder,此handler的作用是如果没有找到特定的映射,它将会进行处理。
				setDefaultHandler(resolvedHandler);
			}else {
				// 加入映射缓存表中
				this.handlerMap.put(urlPath, resolvedHandler);
				if (getPatternParser() != null) {
					this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
				}
			}
		}
	}

BeanNameUrlHandlerMapping继承AbstractDetectingUrlHandlerMapping,容器初始化的时候将会根据配置信息,加载映射关系加入缓存。

	AbstractDetectingUrlHandlerMapping:
	public void initApplicationContext() throws ApplicationContextException {
		super.initApplicationContext();
		detectHandlers();
	}
	
	protected void detectHandlers() throws BeansException {
		ApplicationContext applicationContext = obtainApplicationContext();
		// detectHandlersInAncestorContexts默认为false,为true时查找范围将包括父容器
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
				applicationContext.getBeanNamesForType(Object.class));
		for (String beanName : beanNames) {
			String[] urls = determineUrlsForHandler(beanName);
			if (!ObjectUtils.isEmpty(urls)) {
				// 这里就会调用父类通用注册逻辑
				registerHandler(urls, beanName);
			}
		}
	}
	
	/**
	  * eg:url为/search,那么handler的beanName要么为/search,要么别名为/search
	  */
	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);
	}

上面这种方式太死板,严格要求请求url与处理类beanName一致,不推荐使用;SimpleUrlHandlerMapping相比较而言,就显的灵活多了。

	public void initApplicationContext() throws BeansException {
		super.initApplicationContext();
		registerHandlers(this.urlMap);
	}
	
	/**
	 * eg: url为/add,handler的banName可以指定任意名称,只需两者之间建立映射关系即可
  	 */
	protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
		if (urlMap.isEmpty()) {
			logger.trace("No patterns in " + formatMappingName());
		}else {
			urlMap.forEach((url, handler) -> {
				if (!url.startsWith("/")) {
					url = "/" + url;
				}
				if (handler instanceof String) {
					handler = ((String) handler).trim();
				}
				// 这里就会调用父类通用注册逻辑
				registerHandler(url, handler);
			});
			//...日志相关
		}
	}

而springMVC中构建映射关系使用到这个的有ViewControllerRegistry,ResourceHandlerRegistry,DefaultervletHandlerConfigurer。它们3个构建映射缓存时,使用到的处理器分别为Parameterizable-ViewController,ResourceHttpRequestHandler,DefaultServletHttpRequestHandler。这3个处理器都能处理静态资源,但是处理方式不同,一般使用时优先级也是按照我介绍顺序一样,springBoot中就是这么干的,下面我们简单的了解下这3个类是如何处理静态资源请求的。

  • ParameterizableViewController
	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		// 获取设置的视图名
		String viewName = getViewName();

		if (getStatusCode() != null) {
			// 状态码是否设置的重定向
			if (getStatusCode().is3xxRedirection()) {
				request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, getStatusCode());
			}else {
				response.setStatus(getStatusCode().value());
				// 响应状态码设置为204 并且没有视图设置
				if (getStatusCode().equals(HttpStatus.NO_CONTENT) && viewName == null) {
					return null;
				}
			}
		}
		// 表示请求是否在此控制器中进行处理
		if (isStatusOnly()) {
			return null;
		}
		// 返回ModelAndView
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addAllObjects(RequestContextUtils.getInputFlashMap(request));
		if (viewName != null) {
			modelAndView.setViewName(viewName);
		}else {
			modelAndView.setView(getView());
		}
		return modelAndView;
	}

我们需要做的只是配置好视图名称,响应状态码(可配可不配)即可,当需要进行重定向时,可以配置视图名称为"redirect:viewName",也可以将状态码配置为3xx。

	public String getViewName() {
		if (this.view instanceof String) {
			String viewName = (String) this.view;
			if (getStatusCode() != null && getStatusCode().is3xxRedirection()) {
				// 状态码为3xx即使没有redirect:也会帮你拼接上
				return viewName.startsWith("redirect:") ? viewName : "redirect:" + viewName;
			}else {
				return viewName;
			}
		}
		return null;
	}
  • ResourceHttpRequestHandler
	public void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 获取静态资源
		Resource resource = getResource(request);
		if (resource == null) {
			logger.debug("Resource not found");
			// 不存在响应404
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		if (HttpMethod.OPTIONS.matches(request.getMethod())) {
			response.setHeader("Allow", getAllowHeader());
			return;
		}
		// 检查请求方法和会话是否支持
		checkRequest(request);

		// 校验请求内容是否有修改,没有则利用浏览器缓存
		if (isUseLastModified() && new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
			logger.trace("Resource not modified");
			return;
		}

		// 应用缓存设置(如果有的话)
		prepareResponse(response);

		// 检查资源的媒体类型
		MediaType mediaType = getMediaType(request, resource);
		// 根据资源的媒体类型设置响应头
		setHeaders(response, resource, mediaType);
		
		ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
		if (request.getHeader(HttpHeaders.RANGE) == null) {
			Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
			this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
		}else {
			// 请求头含有HttpHeaders.RANGE,表示断点续传
			Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
			ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
			try {
				List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
				// 断点续传响应的状态码并不是200,而是206,206表示服务器已经完成部分获取资源请求。
				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				this.resourceRegionHttpMessageConverter.write(
						HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
			}catch (IllegalArgumentException ex) {
				response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
				response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
			}
		}
	}

XML中使用<mvc:resources />命名空间进行配置,最终调用的就是上面这段代码。

关于断点续传的知识点,大家自行百度,我这里先Mark下。这里推荐一篇了解基础概念的博客。

  • DefaultServletHttpRequestHandler
	public void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		Assert.state(this.servletContext != null, "No ServletContext set");
		// 获取到Servlet容器缺省的Servlet,像Tomcat, Jetty, JBoss, and GlassFish容器默认servletName为default
		RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
		if (rd == null) {
			throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
					this.defaultServletName + "'");
		}
		rd.forward(request, response);
	}

XML中使用<mvc:default-servlet-handler />命名空间进行配置,使用的就是DefaultServletHttpReque-stHandler。

请求到处理方法

在这里插入图片描述
根据类依赖关系图我们可以发现在AbstractHandlerMapping基础上抽象了一个类AbstractHandlerMe-thodMapping,继续啃源码。

AbstractHandlerMethodMapping:
	@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}
	protected void initHandlerMethods() {
		String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
		for (String beanName : beanNames) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				Class<?> beanType = null;
				try {
					beanType = obtainApplicationContext().getType(beanName);
				}catch (Throwable ex) {
					//...
				}
				// isHandler方法就是判断类上是否有@Controller或者@RequestMapping注解
				if (beanType != null && isHandler(beanType)) {
					detectHandlerMethods(beanName);
				}
			}
		}
		// 留给子类拓展 在检测到所有处理程序方法后调用。
		handlerMethodsInitialized(getHandlerMethods());
	}

	protected void detectHandlerMethods(final Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			final Class<?> userType = ClassUtils.getUserClass(handlerType);
			// MetadataLookup是一个函数式回调接口 这里定义的逻辑就是获取方法的映射信息,getMappingForMethod留给子类拓展
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
			if (logger.isTraceEnabled()) {
				logger.trace(formatMappings(userType, methods));
			}
			methods.forEach((method, mapping) -> {
				// 找到能调用的方法
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// 注册处理方法及其唯一映射
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}
	
	public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
		final Map<Method, T> methodMap = new LinkedHashMap<>();
		Set<Class<?>> handlerTypes = new LinkedHashSet<>();
		Class<?> specificHandlerType = null;

		if (!Proxy.isProxyClass(targetType)) {
			// targetType只要不是CGLIB代理类,就返回自己,否则返回原始类
			specificHandlerType = ClassUtils.getUserClass(targetType);
			handlerTypes.add(specificHandlerType);
		}
		handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));

		for (Class<?> currentHandlerType : handlerTypes) {
			final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

			ReflectionUtils.doWithMethods(currentHandlerType, method -> {
				// 获取最明确的目标方法
				Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
				// 用于查找目标方法上的元数据信息
				T result = metadataLookup.inspect(specificMethod);
				if (result != null) {
					// 关于桥接方法大家可以去看这篇博客 https://blog.csdn.net/mhmyqn/article/details/47342577
					Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
					if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
						// 这里将收集到的方法以及对应的元数据信息 加入缓存中
						methodMap.put(specificMethod, result);
					}
				}
				// 最后一个参数是方法过滤器,这里是要求非桥接方法才去调用方法回调函数
			}, ReflectionUtils.USER_DECLARED_METHODS);
		}

		return methodMap;
	}

	public void register(T mapping, Object handler, Method method) {
			this.readWriteLock.writeLock().lock();
			try {
				// 构建处理方法
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				// 校验唯一性 一个处理方法对应一段映射
				validateMethodMapping(handlerMethod, mapping);
				// 获取请求路径
				Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
				for (String path : directPaths) {
					this.pathLookup.add(path, mapping);
				}
				// 提取并返回映射的CORS配置。
				CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
				if (config != null) {
					config.validateAllowCredentials();
					this.corsLookup.put(handlerMethod, config);
				}
				this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths));
			}finally {
				this.readWriteLock.writeLock().unlock();
			}
		}

通过上面的代码分析,我们可以了解到获取方法元信息是留给子类拓展实现的,这是构建请求url与请求方法映射关系的关键代码。

RequestMappingHandlerMapping:
	 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   		// 获取方法上的@RequestMapping信息
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			// 获取类上的@RequestMapping信息
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				// 两者合并
				info = typeInfo.combine(info);
			}
			// 我们可以给不同的handler配置不同的路径前缀,只要value设置的值匹配即可
			for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
				if (entry.getValue().test(handlerType)) {
					String prefix = entry.getKey();
					if (this.embeddedValueResolver != null) {
						prefix = this.embeddedValueResolver.resolveStringValue(prefix);
					}
					info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
					break;
				}
			}
		}
		return info;
	}

关于RequestMappingHandlerMapping的内容个人觉得了解这么多已经够了,如果想了解的更深,
可以看这篇博客

另外提一句,Spring 3.0.x之后的版本中如果使用XML配置,请使用<mvc:annotation-driven />,它会为我们引入RequestMappingHandlerMapping。

5.2版本引入新的HandlerMapping之RouterFunctionMapping

响应式编程目前不是很熟悉,留待以后补充


知其然,知其所以然,学才不倦。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值