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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家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>
相关文章
|
3月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
68 4
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
85 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
165 5
|
3月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
97 2
|
4月前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
69 10
|
4月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
3月前
|
前端开发 Java
【案例+源码】详解MVC框架模式及其应用
【案例+源码】详解MVC框架模式及其应用
240 0
|
4月前
|
负载均衡 网络协议 应用服务中间件
web群集--rocky9.2源码部署nginx1.24的详细过程
Nginx 是一款由 Igor Sysoev 开发的开源高性能 HTTP 服务器和反向代理服务器,自 2004 年发布以来,以其高效、稳定和灵活的特点迅速成为许多网站和应用的首选。本文详细介绍了 Nginx 的核心概念、工作原理及常见使用场景,涵盖高并发处理、反向代理、负载均衡、低内存占用等特点,并提供了安装配置教程,适合开发者参考学习。
|
5月前
|
Java Spring 开发者
Java Web开发新潮流:Vaadin与Spring Boot强强联手,打造高效便捷的应用体验!
【8月更文挑战第31天】《Vaadin与Spring Boot集成:最佳实践指南》介绍了如何结合Vaadin和Spring Boot的优势进行高效Java Web开发。文章首先概述了集成的基本步骤,包括引入依赖和配置自动功能,然后通过示例展示了如何创建和使用Vaadin组件。相较于传统框架,这种集成方式简化了配置、提升了开发效率并便于部署。尽管可能存在性能和学习曲线方面的挑战,但合理的框架组合能显著提升应用开发的质量和速度。
117 0
|
5月前
|
消息中间件 Java Kafka
Spring Boot与模板引擎:整合Thymeleaf和FreeMarker,打造现代化Web应用
【8月更文挑战第29天】这段内容介绍了在分布式系统中起到异步通信与解耦作用的消息队列,并详细探讨了三种流行的消息队列产品:RabbitMQ、RocketMQ 和 Kafka。RabbitMQ 是一个基于 AMQP 协议的开源消息队列系统,支持多种消息模型,具有高可靠性及稳定性;RocketMQ 则是由阿里巴巴开源的高性能分布式消息队列,支持事务消息等多种特性;而 Kafka 是 LinkedIn 开源的分布式流处理平台,以其高吞吐量和良好的可扩展性著称。文中还提供了使用这三种消息队列产品的示例代码。
41 0