【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(一)---BeanNameUrlHandlerMapping系列(上)

简介: 【小家Spring】Spring MVC容器的web九大组件之---HandlerMapping源码详解(一)---BeanNameUrlHandlerMapping系列(上)

前言


在这篇文章里:

【小家Spring】Spring MVC容器启动时,web九大组件初始化详解(Spring MVC的运行机制)

已经大概介绍过web九大组件,本文将聚焦于Spring MVC中最重要的一个组件:HandlerMapping展开讨论


HandlerMapping


用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事


HandlerMapping:负责映射用户的URL和对应的处理类Handler,HandlerMapping并没有规定这个URL与应用的处理类如何映射。所以在HandlerMapping接口中仅仅定义了根据一个URL必须返回一个由HandlerExecutionChain代表的处理链,我们可以在这个处理链中添加任意的HandlerAdapter实例来处理这个URL对应的请求(这样保证了最大的灵活性映射关系)。


public interface HandlerMapping {
  //@since 4.3.21
  String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
  String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
  String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
  String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
  String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
  String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
  // 该接口提供的唯一一个方法~~~~
  @Nullable
  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}


看看它的继承树:


image.png



它有两大继承主线:MatchableHandlerMapping和AbstractHandlerMapping


AbstractHandlerMapping


这是Spring的常用模式了,一言不合就先来个抽象实现。查看它的继承图谱:

image.png


有必要先对两个XXXSupport进行一个非常简单的说明~


WebApplicationObjectSupport和ApplicationObjectSupport

看他两的申明,他俩更像是ApplicationContextAware和ServletContextAware的适配器

public abstract class ApplicationObjectSupport implements ApplicationContextAware { ... }
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware { ... }


所以我们如果我们在继承允许的情况下,只需要继承此类就能自动拥有上面两个接口的功能了。


@Service
public class HelloServiceImpl extends WebApplicationObjectSupport implements HelloService {
    @Override
    public Object hello() {
        // 继承自ApplicationObjectSupport就可以很方便的获取到下面这两个值
        System.out.println(super.getApplicationContext());
        System.out.println(super.obtainApplicationContext()); //@since 5.0
        // MessageSourceAccessor参考:MessageSourceAware   它是对MessageSource的一个包装  处理国际化
        System.out.println(super.getMessageSourceAccessor());
        // 这里需要继承和web相关的:WebApplicationObjectSupport
        System.out.println(super.getWebApplicationContext());
        System.out.println(super.getServletContext());
        System.out.println(super.getTempDir()); //Tomcat9_demowar\work\Catalina\localhost\demo_war_war
        return "service hello";
    }
    @Override
    protected void initApplicationContext() throws BeansException {
        // 这是父类提供给子类的(父类为空实现~),子类可以自行实现,实现子类的逻辑
        // 比如子类AbstractDetectingUrlHandlerMapping就复写了此方法去detectHandlers();
        super.initApplicationContext();
    }
}


就这样可以通过继承的方式快速的实现获取上下文等,推荐使用~~~


WebApplicationObjectSupport用于提供上下文ApplicationContext和ServletContext的功能~

很显然如果你已经有继承了,那就没办法只能选择实现接口的方式了~


继续来看看AbstractHandlerMapping这个抽象实现给我们做了哪些事情~


// 它自己又额外实现了BeanNameAware和Ordered排序接口
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
    implements HandlerMapping, Ordered, BeanNameAware {
  //默认的Handler,这边使用的Obejct,子类实现的时候,使用HandlerMethod,HandlerExecutionChain等
  // the default handler for this handler mapping
  @Nullable
  private Object defaultHandler;
  // url路径计算的辅助类、工具类
  private UrlPathHelper urlPathHelper = new UrlPathHelper();
  // Ant风格的Path匹配模式~  解决如/books/{id}场景
  private PathMatcher pathMatcher = new AntPathMatcher();
  // 保存着拦截器们~~~
  private final List<Object> interceptors = new ArrayList<>();
  // 从interceptors中解析得到,直接添加给全部handler
  private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
  // 跨域相关的配置~
  private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
  private CorsProcessor corsProcessor = new DefaultCorsProcessor();
  // 最低的顺序(default: same as non-Ordered)
  private int order = Ordered.LOWEST_PRECEDENCE;
  @Nullable
  private String beanName;
  ...
  // 关于UrlPathHelper 的属性的一些设置~~~
  public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {...}
  public void setUrlDecode(boolean urlDecode) { ... }
  public void setRemoveSemicolonContent(boolean removeSemicolonContent) { ... }
  public void setUrlPathHelper(UrlPathHelper urlPathHelper) { ... } //我们也是可议自己指定一个自己的UrlPathHelper 的
  ...
  // PathMatcher我们也可以自己指定
  public void setPathMatcher(PathMatcher pathMatcher) { ... }
  // Set the interceptors to apply for all handlers mapped by this handler mapping
  // 可变参数:可以一次性添加多个拦截器~~~~  这里使用的Object
  public void setInterceptors(Object... interceptors) {
    this.interceptors.addAll(Arrays.asList(interceptors));
  }
  // 设值一个UrlBasedCorsConfigurationSource  Map表示它的一些属性们~~~
  public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { ... }
  // 重载方法  @since 5.1  Spring5.1之后才有的方法
  public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
    Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
    this.corsConfigurationSource = corsConfigurationSource;
  }
  // Configure a custom {@link CorsProcessor} to use to apply the matched
  // @since 4.2
  public void setCorsProcessor(CorsProcessor corsProcessor) {
    Assert.notNull(corsProcessor, "CorsProcessor must not be null");
    this.corsProcessor = corsProcessor;
  }
  ...
  // 这步骤是最重要的。相当于父类setApplicationContext完成了之后,就会执行到这里~~~
  // 这这步骤可议看出   这里主要处理的都是拦截器~~~相关的内容
  @Override
  protected void initApplicationContext() throws BeansException {
    // 给子类扩展:增加拦截器,默认为空实现
    extendInterceptors(this.interceptors);
    // 找到所有MappedInterceptor类型的bean添加到adaptedInterceptors中
    detectMappedInterceptors(this.adaptedInterceptors);
    // 将interceptors中的拦截器取出放入adaptedInterceptors
    // 如果是WebRequestInterceptor类型的拦截器  需要用WebRequestHandlerInterceptorAdapter进行包装适配
    initInterceptors();
  }
  // 去容器(含祖孙容器)内找到所有的MappedInterceptor类型的拦截器出来,添加进去   非单例的Bean也包含
  // 备注MappedInterceptor为Spring MVC拦截器接口`HandlerInterceptor`的实现类  并且是个final类 Spring3.0后出来的。
  protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
    mappedInterceptors.addAll(
        BeanFactoryUtils.beansOfTypeIncludingAncestors(
            obtainApplicationContext(), MappedInterceptor.class, true, false).values());
  }
  // 它就是把调用者放进来的interceptors们,适配成HandlerInterceptor然后统一放在`adaptedInterceptors`里面装着~~~
  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));
      }
    }
  }
  // 适配其实也很简单~就是支持源生的HandlerInterceptor以及WebRequestInterceptor两种情况而已
  protected HandlerInterceptor adaptInterceptor(Object interceptor) {
    if (interceptor instanceof HandlerInterceptor) {
      return (HandlerInterceptor) interceptor;
    } else if (interceptor instanceof WebRequestInterceptor) {
      // WebRequestHandlerInterceptorAdapter它就是个`HandlerInterceptor`,内部持有一个WebRequestInterceptor的引用而已
      // 内部使用到了DispatcherServletWebRequest包request和response包装成`WebRequest`等等
      return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    } else {
      throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
    }
  }
  protected final HandlerInterceptor[] getAdaptedInterceptors() { ... }
  // 它只会返回MappedInterceptor这种类型的,上面是返回adaptedInterceptors所有
  protected final MappedInterceptor[] getMappedInterceptors() { ... }
  // 这个方法也是一个该抽象类提供的一个非常重要的模版方法:根据request获取到一个HandlerExecutionChain
  // 也是抽象类实现接口HandlerMapping的方法~~~
  @Override
  @Nullable
  public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 根据request获取对应的handler   抽象方法,由具体的子类去实现~~~~
    Object handler = getHandlerInternal(request);
    // 若没有匹配上处理器,那就走默认的处理器~~~   默认的处理器也是需要由子类给赋值  否则也会null的
    if (handler == null) {
      handler = getDefaultHandler();
    }
    // 若默认的处理器都木有  那就直接返回null啦~
    if (handler == null) {
      return null;
    }
    // 意思是如果是个String类型的名称,那就去容器内找这个Bean,当作一个Handler~
    if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
    }
    // 关键步骤:根据handler和request构造一个请求处理链~~
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    // 4.2版本提供了对CORS跨域资源共享的支持  此处暂时略过~
    if (CorsUtils.isCorsRequest(request)) {
      ...
    }
    return executionChain;
  }
  // 已经找到handler了,那就根据此构造一个请求链
  // 这里主要是吧拦截器们给糅进来~  构成对指定请求的一个拦截器链
  protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    // 小细节:因为handler本身也许就是个Chain,所以此处需要判断一下~
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    // 此处就用到了urlPathHelper来解析request 
    // 如我的请求地址为:`http://localhost:8080/demo_war_war/api/v1/hello`  那么lookupPath=/api/v1/hello
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
      if (interceptor instanceof MappedInterceptor) {
        // 这里其实就能体现出MappedInterceptor的些许优势了:也就是它只有路径匹配上了才会拦截,没有匹配上的就不会拦截了,处理起来确实是更加的优雅些了~~~~
        // 备注:MappedInterceptor可以设置includePatterns和excludePatterns等~~~~~
        MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
        if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
          chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
      } else {
        chain.addInterceptor(interceptor);
      }
    }
    return chain;
  }
  ...
}


关于Web相关使用到的工具类,也可以参考到这里的:

【小家Spring】Spring MVC好用工具介绍:UrlPathHelper、WebUtils、RequestContextUtils、WebApplicationContextUtils…


AbstractHandlerMapping主要实现了对方法getHandler()的模版实现,它主要是对HandlerInterceptor进行了一个通用处理,最终会把他们放进HandlerExecutionChain里面去~~~


MappedInterceptor

一个包括includePatterns和excludePatterns字符串集合并带有HandlerInterceptor功能的类。

很明显,就是对于某些地址做特殊包括和排除的拦截器。

Spring3.0推出的对HandlerInterceptor的实现类,且是个final类。所以扩展它并不是像通过继承HandlerInterceptorAdapter这样去扩展,而是通过了类似代理的方式~~~~

// @since 3.0  它是个final类  所以不允许你直接使用继承的方式来扩展
public final class MappedInterceptor implements HandlerInterceptor {
  // 可以看到它哥俩都是可以不用指定,可以为null的
  @Nullable
  private final String[] includePatterns;
  @Nullable
  private final String[] excludePatterns;
  // 持有一个interceptor的引用,类似于目标类~
  private final HandlerInterceptor interceptor;
  // 注意:该类允许你自己指定路径的匹配规则。但是Spring里,不管哪个上层服务,默认使用的都是Ant风格的匹配
  // 并不是正则的匹配  所以效率上还是蛮高的~
  @Nullable
  private PathMatcher pathMatcher;
  //======构造函数:发现它不仅仅兼容HandlerInterceptor,还可以把WebRequestInterceptor转换成此~
  public MappedInterceptor(@Nullable String[] includePatterns, HandlerInterceptor interceptor) {
    this(includePatterns, null, interceptor);
  }
  ...
  public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns,
      WebRequestInterceptor interceptor) {
    // 此处使用WebRequestHandlerInterceptorAdapter这个适配器~~~
    this(includePatterns, excludePatterns, new WebRequestHandlerInterceptorAdapter(interceptor));
  }
  // 原则:excludePatterns先执行,includePatterns后执行
  // 如果excludePatterns执行完都木有匹配的,并且includePatterns是空的,那就返回true(这是个处理方式技巧~  对这种互斥的情况  这一步判断很关键~~~)  
  public boolean matches(String lookupPath, PathMatcher pathMatcher) { ... }
  ...
}


因为它不能简单的像扩展HandlerInterceptorAdapter一样使用,下面给出一个Demo,推荐大家以后都采用这种方案去更加优雅的处理你的拦截器:


@ComponentScan(value = "com.fsx", useDefaultFilters = false,
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
)
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
     方式一:最源生的使用方式:直接注册进去即可
     其实它也挺强大,支持includePatterns和exclude...
     其实它底层原理是一个依赖于`InterceptorRegistration`,它是个普通类,协助create一个`MappedInterceptor`
     由此可见最终底层还是使用的`MappedInterceptor`哦~~~~~
    //@Override
    //public void addInterceptors(InterceptorRegistry registry) {
    //    registry.addInterceptor(new HelloInterceptor())
    //            .addPathPatterns() // 就是includePatterns
    //            .excludePathPatterns();
    //}
    // 方式二:如果说上述方式是交给Spring去帮我自动处理,这种方式相当于自己手动来处理~~~~~
    // 请务必注意:  请务必注意:此处的返回值必须是MappedInterceptor,而不能是HandlerInterceptor  否则不生效~~~
    // 因为BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false)
    // 这个方法它只去找MappedInterceptor类型,如果你是父类型,那就匹配不上了的~~~  这个工厂方法的Bean定义信息有关~~
    @Bean
    public MappedInterceptor myHandlerInterceptor() {
        String[] includePatterns = {"/api/v1/hello"};
        MappedInterceptor handlerInterceptor = new MappedInterceptor(includePatterns, new HelloInterceptor());
        return handlerInterceptor;
    }
}

HelloInterceptor自己的业务拦截逻辑写在自己里面即可。**需要注意的是,**若你的拦截器里想去使用Spring容器内的其它Bean,请不用使用new的方式,而是应该交给Spring管理(可用@Component)。然后此处可写如方法入参或者通过@Autowired的方式注入进来~~~~

提供了两种方法,但推荐使用方式一,直观且不容易出错些~~~


Tips:在基于XML的配置中,如下配置其实使用的就是MappedInterceptor


<mvc:interceptors>
  <mvc:interceptor>
      <!--拦截器mapping 符合的才会执行拦截器-->
      <mvc:mapping path="/**"/>
          <!--在拦截器mapping中除去下面的url -->
          <mvc:exclude-mapping path="/transactional_test/*"/>
          <!--执行的拦截器(其实这个Bean并没有必要放进容器里面)-->
          <ref bean="apiInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
相关文章
|
12月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
12月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
运维 数据可视化 C++
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
2025年热门Web化容器部署工具对比:Portainer与Websoft9。Portainer以轻量可视化管理见长,适合技术团队运维;Websoft9则提供一站式应用部署与容器管理,内置丰富开源模板,降低中小企业部署门槛。两者各有优势,助力企业提升容器化效率。
753 1
2025 热门的 Web 化容器部署工具对比:Portainer VS Websoft9
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
495 1
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
326 0
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
977 0
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
443 0
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
1097 29
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
1273 7
Spring Boot 入门:简化 Java Web 开发的强大工具
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
1293 7