【探索Spring底层】13.深入DispatcherServlet

导读:本篇文章讲解 【探索Spring底层】13.深入DispatcherServlet,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1. DispatcherServlet概述

DispatcherServlet是SpringMVC的核心——前端控制器,但是其本质其实也就是一个HttpServlet。它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。

DispatcherServlet作为统一访问点,主要进行全局的流程控制。

img


2. DispatcherServlet的初始化时机

DispatcherServlet什么时候初始化呢?

下面我们通过实践来得出结果

首先准备一个内嵌tomcat的容器

public class A20 {
    private static final Logger log = LoggerFactory.getLogger(A20.class);

    public static void main(String[] args) throws Exception {
        //AnnotationConfig:支持java配置类的形式来构建容器
        //ServletWebServer:支持内嵌容器
     AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    }
}

AnnotationConfigServletWebServerApplicationContext这个类是可以基于Java配置类的形式构造容器,同时也支持内嵌容器。

详情可以查看:【探索Spring底层】2.容器的实现_起名方面没有灵感的博客-CSDN博客

接下来编写配置类

@Configuration
@ComponentScan
public class WebConfig {
    // ⬅️内嵌 web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactory(serverProperties.getPort());
    }

    // ⬅️创建 DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // ⬅️注册 DispatcherServlet, Spring MVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        return registrationBean;
    }
}

完成这些之后启动A20main方法

image-20221220153620153

可以发现tomcat启动之后,DispatcherServlet并无初始化。

通过日志我们可以发现启动的服务的端口为8080,这时候我们在浏览器访问localhost:8080

当在浏览器发起请求的一瞬间,观察控制台可以发现DispatcherServlet开始初始化了。

image-20221220153901600

因此不难看出,在首次使用到DispatcherServlet的时候,才会由tomcat服务器来初始化

在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化

那么有没有可不可以修改DispatcherServlet初始化的时机呢,让它在tomcat启动的时候就初始化

答案是可以的

@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
    DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
    DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    //在tomcat启动的时候就初始化DispatcherServlet
    registrationBean.setLoadOnStartup(1);
    return registrationBean;
}

3. DispatcherServlet初始化执行的操作

DispatcherServlet的初始化操作,离不开这个类中的onRefresh方法

@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
    //文件上传
    initMultipartResolver(context);
    //本地化信息
    initLocaleResolver(context);
    initThemeResolver(context);
    //做路径映射
    initHandlerMappings(context);
    //适配不同形式的控制器方法
    initHandlerAdapters(context);
    //解析异常
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

initStrategies方法中分别调用了9个方法,这些方法都类似,9个方法其实也就是初始化9类组件

就下面的initHandlerMappings来说

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
	//判断是否要到父容器中寻找
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        //在当前容器找
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            //容器中有优先使用容器的
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    //如果容器中没有
    if (this.handlerMappings == null) {
        //使用默认的
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }

    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

不难看出,这里九大组件都是使用默认的。image-20221220160026077


4. RequestMappingHandlerMapping 基本用途

RequestMappingHandlerMapping的作用是在容器启动后将系统中所有控制器方法的请求条件(RequestMappingInfo)和控制器方法(HandlerMethod)的对应关系注册到RequestMappingHandlerMapping Bean的内存中,待接口请求系统的时候根据请求条件和内存中存储的系统接口信息比对,再执行对应的控制器方法。

其工作流程如下:先到当前容器下找到所有的控制类,然后进一步查看这个控制类有哪一些方法,如果方法上加了如@GetMapping等,便会将其记录下来,记录它们的路径、对应的控制器方法

// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰
// ⬅️1. 加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    return new RequestMappingHandlerMapping();
}

前面说到九大组件会先在容器中寻找看看有没有对应的Bean,如果没有就会使用默认的,但是使用默认的话,创建出来的RequestMappingHandlerMapping会直接称为DispatcherServlet的其中一个成员变量,在容器中无法获取,对我们的测试有很大影响,因此需要我们自己将RequestMappingHandlerMapping放到容器中。

public class A20 {
    private static final Logger log = LoggerFactory.getLogger(A20.class);

    public static void main(String[] args) throws Exception {
        //AnnotationConfig:支持java配置类的形式来构建容器
        //ServletWebServer:支持内嵌容器
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

         作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        //
         获取映射结果
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println(k + "=" + v);
        });
    }
}

通过getHandlerMethods就能得到映射结果,运行可发现控制类中的所有请求的路径和对应的方法名都获取成功了

  • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
  • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
  • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet

image-20221220161141153

准备好之后进行一下模拟请求

 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

image-20221220161614148

在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet


5. RequestMappingHandlerAdapter 基本用途

RequestMappingHandlerAdapter翻译过来叫处理器适配器,其作用就是去调用被@RequestMapping标注的控制器方法

同样的,先将其放在容器中

RequestMappingHandlerAdapter 里面有个非常重要的方法protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod),但是这个是保护方法,可以稍微做处理方法作用域

public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}

放在容器中

@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {

    MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();

    return handlerAdapter;
}

发送请求

 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");

MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
//
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
 HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

image-20221220162807858

test1方法确实被调用了!!!


6. 参数和返回值解析器

通过这两个方法就可以的出当前所有的参数解析器和返回值解析器了

System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");
for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
    System.out.println(resolver);
}

System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");
for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
    System.out.println(handler);
}

image-20221220163119653

但是要想进一步了解其是怎么实现的,最好的方法就是模拟实现

@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
    log.debug("test3({})", token);
    return null;
}

这里准备一个映射地址/test3,里面的@Token是为了获取请求头的Token,怎么实现呢?

首先先来实现一下参数解析器

自定义一个TokenArgumentResolver实现HandlerMethodArgumentResolver接口

public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    // 是否支持某个参数
    public boolean supportsParameter(MethodParameter parameter) {
        Token token = parameter.getParameterAnnotation(Token.class);
        return token != null;
    }

    @Override
    // 解析参数
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getHeader("token");
    }
}

这里是先执行supportsParameter方法,判断是否有@Token注解,如果有才会执行resolveArgument方法

这样

虽然这个参数解析器完成了,我们还需要配置一下,因此MyRequestMappingHandlerAdapter并不知有新的参数解析器

@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
    MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));
    return handlerAdapter;
}

这样一个参数解析器就搞好了

接下来实现一下返回值解析器

返回值处理器可以根据返回值不同而作出不同的选择。

通过也可以根据方法上是否有某一注解做出一些不同的处理。

@RequestMapping("/test4")
@Yml
public User test4() {
    log.debug("test4");
    return new User("张三", 18);
}
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    //判断是否有注解
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml != null;
    }

    @Override                   //  返回值
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 1. 转换返回结果为 yaml 字符串
        String str = new Yaml().dump(returnValue);

        // 2. 将 yaml 字符串写入响应
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(str);

        // 3. 设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
    YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
    MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    handlerAdapter.setCustomArgumentResolvers(List.of(tokenArgumentResolver));
    handlerAdapter.setCustomReturnValueHandlers(List.of(ymlReturnValueHandler));
    return handlerAdapter;
}

具体操作和参数解析器类似。


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94948.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!