SpringMVC源码系列:AbstractHandlerMapping

2,004 阅读11分钟

AbstractHandlerMapping是实现HandlerMapping接口的一个抽象基类。支持排序,默认处理程序,处理程序拦截器,包括由路径模式映射的处理程序拦截器。所有的HandlerMapping都继承自AbstractHandlerMapping。另外,此基类不支持PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE的暴露,此属性的支持取决于具体的子类,通常基于请求URL映射。

前面说到,HandlerMapping的作用就是通过request查找Handler和Interceptors。具体的获取均是通过子类来实现的。

1.AbstractHandlerMapping 的类定义

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
 implements HandlerMapping, Ordered {

AbstractHandlerMapping继承了WebApplicationObjectSupport,初始化时会自动调用模板方法initApplicationContext;AbstractHandlerMapping的创建也就是在这个方法里面完成的。同时实现了HandlerMapping和Ordered接口,这也就是上面提到的支持排序的原因。

2.AbstractHandlerMapping属性分析

  • 排序值 order

    默认值为Integer的最大值,后面注释的意思是和没有排序是一样的,因为只有理论上才可能超过Integer.MAX_VALUE。

    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
    
  • 默认处理器 defaultHandler

    private Object defaultHandler;
    
  • Spring工具类 urlPathHelper

    Helper类用于URL路径匹配。提供对RequestDispatcher中URL路径的支持,包括并支持一致的URL解码。

    private UrlPathHelper urlPathHelper = new UrlPathHelper();
    
  • spring工具类 PathMatcher(AntPathMatcher)

    用于基于字符串的路径匹配的策略接口。

    private PathMatcher pathMatcher = new AntPathMatcher();
    
  • 拦截器列表 interceptors

    用于配置SpringMVC的拦截器,配置方式由两种:

    • 1.注册HandlerMapping时通过属性设置
    • 2.通过子类的extendInterceptors钩子方法进行设置(extendInterceptors方法是在initApplicationContext中调用的)

    interceptors并不会直接使用,二是通过initInterceptors方法按照类型分配到mappedInterceptors和adaptedInterceptors中进行使用,interceptors只用于配置。

    private final List<Object> interceptors = new ArrayList<Object>();
    
  • adaptedInterceptors

    被分配到adaptedInterceptors中的类型的拦截器不需要进行匹配,在getHandler中会全部添加到返回值HandlerExecutionChain里面。他 只能从 interceptors中获取。

    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();
    
  • corsProcessor

    CorsProcessor作用是接受请求和CorsConfiguration并更新响应的策略。 此组件不关心如何选择CorsConfiguration,而是采取后续操作,例如应用CORS验证检查,并拒绝响应或将CORS头添加到响应中。

    private CorsProcessor corsProcessor = new DefaultCorsProcessor();
    
  • corsConfigSource

    根据路径模式上映射的CorsConfiguration集合提供每个请求的CorsConfiguration实例。支持精确的路径映射URI(如“/ admin”)以及Ant样式的路径模式(如“/ admin / **”)

    private final UrlBasedCorsConfigurationSource corsConfigSource 
    = new UrlBasedCorsConfigurationSource();
    
  • 跨域相关问题

CorsConfiguration 具体封装跨域配置信息的pojo
CorsConfigurationSource request与跨域配置信息映射的容器
CorsProcessor 具体进行跨域操作的类

3.AbstractHandlerMapping 中的get&set方法

3.1 setOrder

指定此HandlerMapping bean的排序值。

public final void setOrder(int order) {
  this.order = order;
}

3.2 setDefaultHandler

指定此HandlerMapping bean的排序值。 设置此处理程序映射的默认处理程序。 如果没有找到特定的映射,这个处理程序将被返回。 缺省值为null,表示没有默认处理程序。

public void setDefaultHandler(Object defaultHandler) {
	this.defaultHandler = defaultHandler;
}

3.3 getDefaultHandler

返回此处理程序映射的默认处理程序,如果没有,则返回null。

public Object getDefaultHandler() {
	return this.defaultHandler;
}

3.4 setAlwaysUseFullPath

如果URL查找始终使用当前servlet上下文中的完整路径,请进行设置。 否则,如果适用,则使用当前servlet映射中的路径(即,在web.xml中“... / *”servlet映射的情况下)。 默认是“false”。setAlwaysUseFullPath中的实现具体是委托给urlPathHelper和corsConfigSource来完成的。

public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
	this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
	this.corsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
}

3.5 setUrlDecode

如果上下文路径和请求URI应该被URL解码,则设置。两者都是由Servlet API返回“undecoded”,与servlet路径相反。根据Servlet规范(ISO-8859-1)使用请求编码或默认编码。setUrlDecode中的实现具体是委托给urlPathHelper和corsConfigSource来完成的。

public void setUrlDecode(boolean urlDecode) {
	this.urlPathHelper.setUrlDecode(urlDecode);
	this.corsConfigSource.setUrlDecode(urlDecode);
}

3.6 setRemoveSemicolonContent

如果“;” (分号)内容应该从请求URI中去除,则设置。默认值是true。setRemoveSemicolonContent中的实现具体是委托给urlPathHelper和corsConfigSource来完成的。

public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
	this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
	this.corsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
}

3.7 setUrlPathHelper

设置UrlPathHelper以用于解析查找路径。 使用它可以用自定义子类覆盖默认的UrlPathHelper,或者跨多个HandlerMappings和MethodNameResolvers共享通用的UrlPathHelper设置。

public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
	Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
	this.urlPathHelper = urlPathHelper;
	this.corsConfigSource.setUrlPathHelper(urlPathHelper);
}

3.8 getUrlPathHelper

返回UrlPathHelper实现以用于解析查找路径。

public UrlPathHelper getUrlPathHelper() {
	return urlPathHelper;
}

3.9 setPathMatcher

将PathMatcher实现设置为用于匹配注册的URL模式的URL路径。 默认是AntPathMatcher。

public void setPathMatcher(PathMatcher pathMatcher) {
	Assert.notNull(pathMatcher, "PathMatcher must not be null");
	this.pathMatcher = pathMatcher;
	this.corsConfigSource.setPathMatcher(pathMatcher);
}

3.10 setInterceptors

设置拦截器以应用此处理程序映射映射的所有处理程序。 支持的拦截器类型是HandlerInterceptor,WebRequestInterceptor和MappedInterceptor。 映射拦截器仅适用于请求与其路径模式相匹配的URL。映射的拦截器Bean在初始化期间也会按类型检测到。

ublic void setInterceptors(Object... interceptors) {
	this.interceptors.addAll(Arrays.asList(interceptors));
}

其他几个get&set方法就不列出来了,有兴趣的小伙伴可以自行阅读...

4. AbstractHandlerMapping的创建

因为AbstractHandlerMapping继承了WebApplicationObjectSupport类,因此AbstractHandlerMapping的创建就是依托于模板方法initApplicationContext来完成的。

@Override
protected void initApplicationContext() throws BeansException {
	extendInterceptors(this.interceptors);
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}

从方法结构可以了解到,initApplicationContext中包括三个子处理方法。

  • extendInterceptors:这也是一个模板方法,在AbstractHandlerMapping中并没有具体实现(方法体是空的),主要是用于给子类提供一个添加(修改)Interceptors的入口(现有的SpringMVC实现中均未使用)。

  • detectMappedInterceptors:用于将SpringMVC容器及父容器中的所有MappedInterceptor类型的Bean添加到MappedInterceptors属性中。

    检测MappedInterceptor类型的bean,并将它们添加到映射的拦截器列表中。 除了可能通过setInterceptors提供的任何MappedInterceptors之外,还会调用此方法,默认情况下将从当前上下文及其祖先中添加所有MappedInterceptor类型的Bean。子类可以覆盖和优化这个策略。

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(
			BeanFactoryUtils.beansOfTypeIncludingAncestors(
					getApplicationContext(), MappedInterceptor.class, true, false).values());
}
  • initInterceptors:初始化指定的拦截器,检查MappedInterceptors并根据需要调整HandlerInterceptors和WebRequestInterceptors。(当前Spring版本时4.3.6)
protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
    	for (int i = 0; i < this.interceptors.size(); i++) {
    		Object interceptor = this.interceptors.get(i);
    		if (interceptor == null) {
    			throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
    		}
    		this.adaptedInterceptors.add(adaptInterceptor(interceptor));
    	}
    }
}

这个是4.1.5版本的initInterceptors方法:

protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
    	for (int i = 0; i < this.interceptors.size(); i++) {
    		Object interceptor = this.interceptors.get(i);
    		if (interceptor == null) {
    			throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
    		}
    		if (interceptor instanceof MappedInterceptor) {
    			this.mappedInterceptors.add((MappedInterceptor) interceptor);
    		}
    		else {
    			this.adaptedInterceptors.add(adaptInterceptor(interceptor));
    		}
    	}
    }
	}

在4.1.5中版本中,initInterceptors的工作是将interceptors属性里面所包含的对象按照类型添加到adaptedInterceptors或者mappedInterceptors中。在4.1.5版本中mappedInterceptors是AbstractHandlerMapping的属性之一。主要原因是因为,springMVC自4.2开始添加了跨域的支持,也就是上面属性中的后两个。PS:在阅读Spring相关源码时需要关注不同版本的变更及区别,不要只关注某一个版本,另外就是个人觉得阅读源码的关注点应该在编码方式、设计模式使用、设计思想及理念,而不仅仅是知道他是如何实现的】

这里顺便说下mappedInterceptors的作用:mappedInterceptors中的拦截器在使用时需要与请求的url进行匹配,只有匹配成功后才会添加到getHandler的返回值HandlerExecytionChain里。

adaptInterceptor方法:

使给定的拦截器对象适配HandlerInterceptor接口。默认情况下,支持的拦截器类型是HandlerInterceptor和WebRequestInterceptor。每个给定的WebRequestInterceptor将被封装在WebRequestHandlerInterceptorAdapter中。可以在子类中重写。

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
	if (interceptor instanceof HandlerInterceptor) {
		return (HandlerInterceptor) interceptor;
	}
	else if (interceptor instanceof WebRequestInterceptor) {
		return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
	}
	else {
		throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
	}
}

5.Handler和Interceptor的获取

HandlerMapping是通过getHandler方法来获取Handler和Interceptor的。因此在抽象基类AbstractHandlerMapping中提供了具体的实现。并且在AbstractHandlerMapping中,getHandler使用final关键字修饰的,也就是说,子类不能再进行对此方法进行覆盖重写了。

getHandler的作用就是查找给定请求的handler,如果找不到特定请求,则返回到默认handler。

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	//通过getHandlerInternal方法来获取handler
	Object handler = getHandlerInternal(request);
	//如果前一个方法没有获取到,则使用默认的handler
	if (handler == null) {
	    //默认的Handler就是AbstractHandlerMapping中的handler属性通过set得到的值
		handler = getDefaultHandler();
	}
	//如果还是没有找到Hander,则直接返回Null
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	//如果找到的handler是String类型的,
	if (handler instanceof String) {
	    //则以它为名到spring Mvc的容器中查找相应的Bean
		String handlerName = (String) handler;
		handler = getApplicationContext().getBean(handlerName);
	}
    //先根据handler和request创建一个HandlerExecutionChain对象,
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

getHandlerInternal

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

查找给定请求的handler,如果找不到特定请求,则返回null。 这个方法被getHandler调用; 如果设置了null返回值,将导致默认handler。 在CORS pre-flight请求上,这个方法应该返回一个不匹配飞行前请求的匹配项,而是根据URL路径,“Access-Control-Request-Method”头中的HTTP方法和头文件 从“Access-Control-Request-Headers”头部获得,从而允许CORS配置通过getCorsConfigurations获得, 注意:这个方法也可以返回一个预先构建的HandlerExecutionChain,将一个处理程序对象与动态确定的拦截器组合在一起。状态指定的拦截器将被合并到这个现有的链中。

getHandlerExecutionChain

getLookupPathForRequest:返回给定请求的映射查找路径,如果适用的话,在当前的servlet映射中,或者在web应用程序中返回。如果在RequestDispatcher中调用include请求,则检测包含请求URL。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    //如果handler是HandlerExecutionChain类型则直接强转为HandlerExecutionChain类型,
        //如果不是则根据handler创建一个新的HandlerExecutionChain实例对象
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    //返回给定请求的映射查找路径
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
	//遍历当前adaptedInterceptors链表
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
	    //如果是MappedInterceptor类型则
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			//拦截器是否应用于给定的请求路径,如果是则返回true
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

为给定的handler构建一个HandlerExecutionChain,包括可用的拦截器。默认实现用给定的handler,handler映射的通用拦截器以及与当前请求URL相匹配的任何MappedInterceptors构建标准的HandlerExecutionChain。拦截器按照他们注册的顺序添加。为了扩展/重新排列拦截器列表,子类可以覆盖它。

需要注意的是,传入的handler对象可能是原始handler或预构建的HandlerExecutionChain。这个方法应该明确地处理这两种情况,建立一个新的HandlerExecutionChain或者扩展现有的链。为了简单地在自定义子类中添加拦截器,可以考虑调用super.getHandlerExecutionChain(handler,request)并在返回的链对象上调用HandlerExecutionChain#addInterceptor。

getCorsHandlerExecutionChain

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
		HandlerExecutionChain chain, CorsConfiguration config) {
    //通过请求头的http方法是否options判断是否预请求,
	if (CorsUtils.isPreFlightRequest(request)) {
		HandlerInterceptor[] interceptors = chain.getInterceptors();
		//如果是使用PreFlightRequest替换处理器
		chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
	}
	else {
    	//如果是普通请求,添加一个拦截器CorsInterceptor。
		chain.addInterceptor(new CorsInterceptor(config));
	}
	return chain;
}

更新HandlerExecutionChain进行与CORS(HTTP访问控制:跨域资源共享)相关的处理。

  • 对于pre-flight请求,默认实现用一个简单的HttpRequestHandler来替换选择的handler,该HttpRequestHandler调用已配置的setCorsProcessor。(将处理器替换为内部类PreFlightHandler)
  • 对于普通的请求,默认实现插入一个HandlerInterceptor,它执行与CORS有关的检查并添加CORS头。(添加CorsInterceptor拦截器)

AbstractHandlerMapping中的两个内部类

这两个内部类就是用来校验request是否cors,并封装对应的Adapter的。

  • PreFlightRequest是CorsProcessor对于HttpRequestHandler的一个适配器。这样HandlerAdapter直接使用HttpRequestHandlerAdapter处理。
  • CorsInterceptor 是CorsProcessor对于HandlerInterceptorAdapter的适配器。

具体的类信息如下:

PreFlightHandler

private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {

	private final CorsConfiguration config;

	public PreFlightHandler(CorsConfiguration config) {
		this.config = config;
	}

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
		corsProcessor.processRequest(this.config, request, response);
	}

	@Override
	public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
		return this.config;
	}
}

CorsInterceptor

private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {

	private final CorsConfiguration config;

	public CorsInterceptor(CorsConfiguration config) {
		this.config = config;
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return corsProcessor.processRequest(this.config, request, response);
	}

	@Override
	public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
		return this.config;
	}
}

至此AbstractHandlerMapping中的一些源码就结束了,AbstractHandlerMapping为HandlerMapping的功能提供的一些具体的模板描述,但是具体的细节实现还需要从其子类中来慢慢分析。关于这部分中涉及到的如HandlerExecutionChain,cors跨域等问题,后面会根据实际情况另开篇幅来学习。

大家如果有什么意见或者建议可以在下方评论区留言,也可以给我们发邮件(glmapper_2018@163.com)!欢迎小伙伴与我们一起交流,一起成长。