写在前面
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
响应式编程目前不是很熟悉,留待以后补充
知其然,知其所以然,学才不倦。