SpringMVC源码系列:AbstractHandlerMapping

Easter79
• 阅读 438

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 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)!欢迎小伙伴与我们一起交流,一起成长。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Java修道之路,问鼎巅峰,我辈代码修仙法力齐天
<center<fontcolor00FF7Fsize5face"黑体"代码尽头谁为峰,一见秃头道成空。</font<center<fontcolor00FF00size5face"黑体"编程修真路破折,一步一劫渡飞升。</font众所周知,编程修真有八大境界:1.Javase练气筑基2.数据库结丹3.web前端元婴4.Jav
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k