Spring mvc 核心 DispatcherServlet 详解

Posted swadian2008

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring mvc 核心 DispatcherServlet 详解相关的知识,希望对你有一定的参考价值。

目录

1、核心Servlet:DispatcherServlet 介绍

2、DispatcherServlet 容器的上下文层次结构

3、特殊类型的 Beans(web mvc 组件)

4、Web MVC 的一些基础配置项

5、Servlet Config 容器的配置

6、DispatcherServlet 请求处理过程

7、有关路径映射的问题

8、拦截器

9、异常处理


1、核心Servlet:DispatcherServlet 介绍

        与许多其他的 web 框架一样,Spring MVC 也是围绕前端控制器模式设计的,核心的 Servlet DispatcherServlet 为请求处理提供了共享算法,但是实际工作由可配置的委托组件执行的。这种设计非常灵活,可以支持不同的工作流。// 核心Servlet,其他组件可配置

        DispatcherServlet 和其他 Servlet 一样,也需要通过一些配置去发现它的委托组件(请求映射、视图解析、异常处理等组件),这些配置可以是 Java 代码,也可以是 web.xml 文件。// 通过配置发现组件

        下面是通过 Java 代码注册和初始化一个 DispatcherServlet 的例子,这些配置信息会被 Servlet 容器自动进行检测:

public class MyWebApplicationInitializer implements WebApplicationInitializer 

    @Override
    public void onStartup(ServletContext servletContext) 

        // 加载Spring web应用程序配置
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // 创建并注册DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        // 定义容器的初始化顺序
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    

2、DispatcherServlet 容器的上下文层次结构

        DispatcherServlet 需要通过 WebApplicationContext 来加载自己的配置。DispatcherServlet 的上下文结构:

        WebApplicationContext 的上下文结构:

        WebApplicationContext 可以通过 getServletContext() 方法访问到关联 Servlet 的上下文。通过 RequestContextUtils 工具类又可以访问到 WebApplicationContext。// Servlet 和上下文绑定

        对于许多应用程序来说,一个 WebApplicationContext 就足够了。但也可以使用一个带层次结构的上下文,在层次结构的上下文中,底层 WebApplicationContext 可以在多个 DispatcherServlet(或其他Servlet)之间进行共享,而每个 Servlet 又都可以有自己单独的子WebApplicationContext 配置。// 分层设计上下文

        底层  WebApplicationContext 通常包含一些基础设施的 Bean,比如跨多个 Servlet 共享的数据存储实例和业务服务等。这些 Bean 可以被有效地继承,并且在特定的 Servlet 的子WebApplicationContext 中进行重写(即重新声明),该子 WebApplicationContext 通常包含给定的 Servlet 的本地 Bean。下图显示了这种关系:

        下面的示例配置了一个 WebApplicationContext 的层次结构:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer 

    @Override
    protected Class<?>[] getRootConfigClasses() 
        return new Class<?>[]  RootConfig.class ;
    

    @Override
    protected Class<?>[] getServletConfigClasses() 
        return new Class<?>[]  App1Config.class ;
    

    @Override
    protected String[] getServletMappings() 
        return new String[]  "/app1/*" ;
    

3、特殊类型的 Beans(web mvc 组件)

        DispatcherServlet 需要通过特殊的 bean 来处理请求和响应。下边列出了 DispatcherServlet 中的一些特殊的 beans:

  1. HandlerMapping:将一个请求映射到另一个处理程序,以及用于拦截器列表的前置处理和后置处理。两个主要的 HandlerMapping 实现,一个是 RequestMappingHandlerMapping,它支持带 @RequestMapping 注释的方法,另一个是 SimpleUrlHandlerMapping,它用来维护 URI 路径到处理程序的显式注册。// 映射请求路径到指定的方法
  2. HandlerAdapter:帮助 DispatcherServlet 调用一个请求映射到的处理程序,该组件并不用管这些处理程序的实际调用(子类有具体的实现的调用逻辑)。例如,调用带注释的控制器需要去解析注释。HandlerAdapter 的主要目的是保护 DispatcherServlet 不受这些细节的影响。// 调用具体的方法
  3. HandlerExceptionResolver:解决异常的策略,可能将异常映射到处理程序、html错误视图或其他目标
  4. ViewResolver:视图解析,用来将程序返回的视图名称解析为对应的实际视图(呈现视图)。
  5. LocaleResolver, LocaleContextResolver:解析客户端正在使用的区域和时区,以便能够提供国际化的视图。// 理解为国际化
  6. ThemeResolver:解决 web 应用程序可以使用的主题。例如,提供个性化的布局等。
  7. MultipartResolver:用于处理文件上传
  8. FlashMapManager:将一个请求发送给另一个请求,通常使用重定向实现。

        ​通过下边的图示,对以上组件的 beans 和 Spring MVC 的执行流程会有一个更加清晰的认知:

4、Web MVC 的一些基础配置项

        应用程序可以声明上述一些特殊类型的 beans,然后 DispatcherServlet 会从容器中去获取这些声明的 bean,如果没有找到对应类型的 bean,DispatcherServlet 就会使用默认的 bean 配置,默认配置的文件内容如下:DispatcherServlet.properties. // 如果不自定义 spring mvc 的组件,那么加载的就是 spring 自己实现的相关组件。

5、Servlet Config 容器的配置

        下面的例子注册了一个DispatcherServlet:// 最原始的方式

public class MyWebApplicationInitializer implements WebApplicationInitializer 

    @Override
    public void onStartup(ServletContext container) 
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    

        WebApplicationInitializer 是 Spring MVC 提供的一个接口,用来为 web 应用程序提供配置。这个接口的实现会自动被 SpringServletContainerInitializer 检测到(SpringServletContainerInitializer 本身会被 Servlet 自动加载)。另外,还可以使用该类的子类 AbstractDispatcherServletInitializer 提供配置,使用该子类配置 Servlet 会更加的方便,示例代码如下:// 代码中 AbstractAnnotationConfigDispatcherServletInitializer 是上述抽象类的子类

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer 

    @Override
    protected Class<?>[] getRootConfigClasses() 
        return null;
    

    @Override
    protected Class<?>[] getServletConfigClasses() 
        return new Class<?>[]  MyWebConfig.class ;
    

    @Override
    protected String[] getServletMappings() 
        return new String[]  "/" ;
    

6、DispatcherServlet 请求处理过程

        DispatcherServlet 处理请求的方式如下:

  1. 为请求绑定 WebApplicationContext 属性。
  2. 为请求绑定 locale resolver(区域解析器),如果不需要请求不要进行区域解析,则不需要区域解析器。
  3. 为请求绑定主题解析器,主要用来决定视图使用哪个主题。如果不使用主题,则可以忽略它。
  4. 如果是文上传请求,此处,该请求会被包装在 MultipartHttpServletRequest 中进行处理。// 点击这里了解文件的处理
  5. 寻找适当的处理程序。如果找到对应的处理程序,则会运行与该处理程序(预处理器、后处理器和控制器)相关的执行链,返回一个用于呈现的模型或结果。
  6. 请求结果处理,如果返回模型,则呈现视图。

        自定义 DispatcherServlet 初始化参数:比如指定 WebApplicationContext 的加载位置,自定义异常处理方式等。

7、有关路径映射的问题

        路径映射涉及都到路径的编码与解码等问题。主要工作是把请求路径和Handler(处理程序/方法)的路径进行匹配,使请求能匹配到指定的处理程序,在后续文章会详细介绍,此处只简要提及。// 详情请点击这里

8、拦截器

        所有 HandlerMapping 的实现(方法)都可以被拦截器进行拦截。定义一个拦截器只需要实现 HandlerInterceptor 接口即可。// 拦截器是全局的,类似于AOP编程

package org.springframework.web.servlet;
public interface HandlerInterceptor 
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        return true;
    

    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 
    

拦截器与过滤器的区别:

        过滤器是Servlet规范,拦截器是Spring组件

具体区别图示:

        使用的拦截器的更多注意事项,点击这里

9、异常处理

        如果在请求期间抛出异常,DispatcherServlet 会将异常委托给 HandlerExceptionResolver bean 链来进行处理。HandlerExceptionResolver 的四个实现

  1. SimpleMappingExceptionResolver:异常类和错误视图之间的映射。用于在浏览器中呈现错误的页面。
  2. DefaultHandlerExceptionResolver:解析Spring MVC引发的异常,并将其映射到HTTP状态代码。
  3. ResponseStatusExceptionResolver:使用@ResponseStatus注释解决异常,并根据注释中的值将异常映射到HTTP状态代码。
  4. ExceptionHandlerExceptionResolver:通过调用@Controller或@ControllerAdvice类中的@ExceptionHandler方法来解决异常。参见@ExceptionHandler方法

        至此,Spring mvc 核心 DispatcherServlet 详解到此结束。

Spring之手写MVC三大核心组件

文章目录

前言

本系列终于迎来了第二个核心功能的编写,首先我们Spring里面一共有三个比较重要的核心功能,首先自然是咱们的IOC反转控制DI依赖注入当然还有AOP切面。这一部分的话我们前面看《Spring5核心原理与30个类手写实战 》是已经熟悉,并且照猫画虎实现了一下的,不过我们此时还没有实现AOP部分,而今天的部分呢是MVC部分,实现咱们最核心的功能之一,当然也是最简单的model,后面还有对数据库的支持。那么基本上三个组件就差不多了,那么后面我再根据自己的理解去升级维护(仿造Spring的真实源码)。虽然我们整个系列,包括这本书其实更多地是偏向这个思想,而不是直接看源码,这样首先是我觉得会一头雾水,不知道为啥这样设计,那么自己先看看书,看看大佬的东西,就会对这个总体的思想有一个认识,然后自己去写,然后去想为什么这样写,然后后面结合实际的源码,知道还能怎样优化。

在这里先建议各位小伙伴先前查看前两篇博文。
Spring之手写SpringMVC5个注解

Spring之手写SpringMVC5个注解(之IOC,DI优化)了解三级缓存

坚持到最后文末获取仓库地址。

流程回顾(MVC)

在此之前我们先简单回顾一下最开始的那个版本对于MVC的处理流程,这个非常重要。

首先我们的MVC,其实有两个部分,一个是我们的初始化部分。初始化部分又是分为两个小部分,第一个小部分自然就是我们的IOC部分,第二个部分就是专属于我们MVC的部分。

之后是我们的分发器,这个是我们MVC的执行组件。

这个就是我们原来的执行流程,显然我们发现整个步骤还是可以优化的,并且我们还可以添加组件。

MVC 最重要的一点是啥,显然是我们的view和model呀,视图加载和数据渲染呀。

SpringMVC九大组件(了解)

在我们进入我们的内容之前,我们还是需要先简单了解一下我们SpringMVC的九大组件的。

     	initMultipartResolver(context) 多文件上传组件
     	initThemeResolver(context) 初始化模板处理器
        //mvc mapping组件(扫描mvc注解参数)
        initHandlerMappings(context);
        // 参数适配器,匹配参数执行方法
        initHandlerAdapters(context);
        initHandleExceptionResolvers(context) 异常拦截器
        initRequestToViewNameTranslator(context) 初始化视图预处理器
        // 初始化视图转换器
        initViewResolvers(context);
        //FlashMap处理器
        initFlashMapManager(context);

那么今天我们显然想要实现的是

initHandlerMappings(context);
initHandlerAdapters(context);
initViewResolvers(context);

新的流程

ok,回到我们这里,既然我们要说优化,那么显然我们需要实现这三个玩意,那么显然我们需要对现在的流程做一点处理。

初始化流程

这里的话我们主要讨论MVC部分的流程,至于IOC,DI的部分咱们就不关注了这里。对应的代码就是这里

组件初始化

初始化干了啥

刚刚看到这个你可能会比较懵逼,那么我门就来说说这三个类到底干了啥。
这里直接看到咱们的流程图

那么整个过程就是我们的初始化部分。

执行流程

之后就是咱们的执行过程

首先其前面两个都是老朋友了,主要是后面我们到底干了啥,也就是我们的视图解析部分,到底干了啥。

首先明确一点,我们的通过初始化已经把我们 的html文件和对应的名字都封装起来了,对应的就是我们的ModelandView


那么model里面是我们需要渲染的数据,这个部分我们接下来是需要通过我们的模板引擎去做的。

而解析这一切的是我们的入口


之后这个view会帮我们做的就是解析模板

而之所以会这样,其实重点的诀窍之一还是在我们的adapte里面

那么这个就是整个MVC的核心流程。

这里的重点其实就是我们几个被封装的“信息类”,就是那些封装信息的类。

“信息类”

HUHandlerMapping

首先自然就是我们的handlermapping 封装了,我们扫描到的其对应的方法和对应的url(这里是做了正则所以其实是模式)之后是它的controller。

HUModelAndView

这个封装的是视图(你可以理解为html)的名字和我们接下来需要交给解析器渲染的数据。

当然与之配套的是HUViewResolver

HUViewResolver 与 HUModelAndView 的关系

其实看名字就知道,前者其实是帮助我们加载 后者对应的html 文件和把要解析的数据给模板引擎的。

解析器 HUView

之后的重点是我们的解析器。这里的话是 HUView 这个类。

package com.huterox.spring.framework.webmvc.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HUView 
    private File viewFile;
    public HUView(File templateFile) 
        this.viewFile = templateFile;
    

    public void render(Map<String,?> model, HttpServletRequest req, HttpServletResponse resp) throws IOException 
        StringBuffer sb = new StringBuffer();//最后输出的内容
        RandomAccessFile ra = new RandomAccessFile(this.viewFile,"r");
        String line = null;
        while (null!=(line=ra.readLine()))
            line = new String(line.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            Pattern pattern = Pattern.compile("\\\\$\\\\[^\\\\]+\\\\",Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(line);
            while (matcher.find())
                String paramName = matcher.group();
                paramName = paramName.replaceAll("\\\\$\\\\|\\\\", "");
                Object paramValue = model.get(paramName);
                line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
                matcher = pattern.matcher(line);
            
            sb.append(line);
        
        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().write(sb.toString());
        resp.getWriter().flush();

    

    public static String makeStringForRegExp(String str)
        //特殊字符转移
        return str.replace("\\\\","\\\\\\\\").replace("*","\\\\*")
                .replace("+","\\\\+").replace("|","\\\\|")
                .replace("","\\\\").replace("","\\\\")
                .replace("(","\\\\(").replace(")","\\\\)")
                .replace("^","\\\\^").replace("$","\\\\$")
                .replace("[","\\\\[").replace("]","\\\\]")
                .replace("?","\\\\?").replace(",","\\\\,")
                .replace(".","\\\\.").replace("&","\\\\&");
    


整个核心是我们的正则表达式
用它来渲染数据。

测试

ok 话不多少进入测试环节

数据渲染ok

项目获取

那么接下来就是获取咱们的项目了。这次的话我是直接上传gitee了,对是直接在gitee

https://gitee.com/Huterox/springcoding

以上是关于Spring mvc 核心 DispatcherServlet 详解的主要内容,如果未能解决你的问题,请参考以下文章

使用java的spring3.05 mvc注解后出现错误:No mapping found for HTTP request with URI

Spring之手写MVC三大核心组件

#yyds干货盘点# Spring核心原理分析之MVC九大组件

Spring MVC的核心类和注解

从Spring到SpringBoot构建WEB MVC核心配置详解

Spring专场「MVC容器」不看源码就带你认识核心流程以及运作原理