SpringMVC之HttpRequestHandler的使用与源码解析

介绍

Spring提供的HttpRequestHandler接口用于用于处理HTTP接口,该接口与HttpServlet等价,其只有一个没有返回值的handleRequest(HttpServletRequest request, HttpServletResponse response)方法;
可以通过与org.springframework.web.context.support.HttpRequestHandlerServlet类来配合使用,使其可以脱离与DispatcherServlet的依赖,配置一个极简的请求处理程序。

可以与DispatcherServlet集成,将HttpRequestHandler接口的具体实现类作为和我们常用的controller同级别的处理程序使用。

由于其没有返回值,所以对于SpringMVC来说永远不会呈现任何视图,所以通常处理POST方式并且直接响应二进制的请求。
在这里插入图片描述

与Controller接口的区别

还有一种与HttpRequestHandler接口高度相似的一个接口同样可以作为SpringMVC的处理程序,这就是Controller接口。
HttpRequestHandler接口一样Controller接口也提供了一个handleRequest(HttpServletRequest request, HttpServletResponse response) 方法来处理http请求;
它们的不同在于:

  • HttpRequestHandlerhandlerReqeust()不提供返回值,而Controller接口需要返回一个ModelAndView
  • 由于HttpRequestHandler接口不提供返回值,所以其定位处理的请求不提供视图;Controller接口需要提供ModelAndView,所以需要呈现一个视图进行相应。
  • HttpRequestHandlerHttpRequestHandlerServlet处于spring-web包中,所以可以脱离SpringMVC的依赖,但Controller接口处于spring-webmvc包中,所以不能脱离SpringMVC来使用。

使用

方式一:极简的请求处理

HttpRequestHandler接口与HttpRequestHandlerServlet类配合使用,不与SpringMVC进行依赖。所以我们可以不用导入spring-webmvc包,只需要导入spring-web即可。

代码

首先我们有一个HttpRequestHandlerHelloWorld实现了HttpRequestHandler接口,并且使用了@Component("helloWorld")来将其作为名为"helloWorld"的bean注册到Spring中,该类的handlerRequest()方法仅仅只是输出一些信息:

package com.baiyang.controller;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

@Component("helloWorld")
public class HttpRequestHandlerHelloWorld implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("helloWorld--->");
        request.getParameterMap().forEach((k, v) -> {
            System.out.println(k + "=" + Arrays.toString(v));
        });
    }
}

然后再一个和上面一样的类用来区分不同的请求:

package com.baiyang.controller;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

@Component("helloWorld2")
public class HttpRequestHandlerHelloWorld2 implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("helloWorld2--->");
        request.getParameterMap().forEach((k, v) -> {
            System.out.println(k + "=" + Arrays.toString(v));
        });
    }
}

以上写好了两个HttpRequestHandler,接下来就是将其加入到Spring根容器中,所以需要写个Spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.baiyang.controller">
    </context:component-scan>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置Spring容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-application.xml</param-value>
    </context-param>

    <!--配置HttpRequestHandlerServlet-->
    <servlet>
	    <!--注意serlvetName必须和Spring容器中的某个HttpRequestHandler的Bean名称一致-->
        <servlet-name>helloWorld</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>helloWorld</servlet-name>
        <url-pattern>/helloWorld</url-pattern>
    </servlet-mapping>
    <servlet>
	    <!--注意serlvetName必须和Spring容器中的某个HttpRequestHandler的Bean名称一致-->
        <servlet-name>helloWorld2</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>helloWorld2</servlet-name>
        <url-pattern>/helloWorld2</url-pattern>
    </servlet-mapping>
</web-app>

需要注意的是,上面的web.xml中配置的servlet的名称需要与Spring容器中的某个HttpRequestHandler对象Bean名称一致。


然后分别请求两个servlet:
POST http://127.0.0.1:8080/proj/helloWorld?a=1&b=2
POST http://127.0.0.1:8080/proj/helloWorld2?a=1&b=2

得请求输出结果:
helloWorld—>
a=[1]
b=[2]
helloWorld2—>
a=[1]
b=[2]

源码解析

直接看HttpRequestHandlerServlet类,可以发现其继承了HttpServlet,所以可以被配置到web.xml中:
在这里插入图片描述
从上面的源码可以看出在Servlet初始化的时候,会获取当前ServletContext中的Spring容器,并且从中获取与当前servletName相同名称的Bean对象,所以在前面的例子中的web.xml中的servlet-name需要与我们写的HttpRequestHandler的Bean名称相同的原因。
然后在service方法中会调用获取的HttpRequestHandler对象的handleRequest方法进行请求处理。


方式二:推荐方式

HttpRequestHandler作为SpringMVC的一种处理方式,交由DispatcherServlet来进行请求转发。这也是推荐的一种方式,这样我们既可以使用到SpringMVC的其他请求处理方式,也可以使用到HttpRequestHandler的处理。

代码

首先我们一样,写两个HttpRequestHandler,但与方式一的区别是,Bean的名称需要加上"/"开头:

package com.baiyang.controller;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

//Bean名称以"/"开头,用来指明该bean处理请求路径为"/helloWorld"
@Component("/helloWorld")
public class HttpRequestHandlerHelloWorld implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("helloWorld--->");
        request.getParameterMap().forEach((k, v) -> {
            System.out.println(k + "=" + Arrays.toString(v));
        });
    }
}
package com.baiyang.controller;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

//Bean名称以"/"开头,用来指明该bean处理请求路径为"/helloWorld2"
@Component("/helloWorld2")
public class HttpRequestHandlerHelloWorld2 implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("helloWorld2--->");
        request.getParameterMap().forEach((k, v) -> {
            System.out.println(k + "=" + Arrays.toString(v));
        });
    }
}

然后是配置web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置SpringMVC-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-application.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

然后启动容器,即可请求:
POST http://127.0.0.1:8080/proj/helloWorld?a=1&b=2
POST http://127.0.0.1:8080/proj/helloWorld2?a=1&b=2

得到结果:
helloWorld—>
a=[1]
b=[2]
helloWorld2—>
a=[1]
b=[2]

源码解析

首先我们需要知道在SpringMVC中涉及到HttpRequestHandler处理流程的关键的几个类:

  • 请求转发核心类->DispatcherServlet
  • 处理HttpRequestHandlerHandlerAdapter->HttpRequestHandlerAdapter
  • 处理HttpRequestHandlerHandlerMapping->BeanNameUrlHandlerMapping

首先是请求的入口:
org.springframework.web.servlet.DispatcherServlet#doService
在这里插入图片描述
org.springframework.web.servlet.DispatcherServlet#doDispatch
在这里插入图片描述
上面是SpringMVC的主要入口逻辑。

接下来是通过请求来获取HandlerMapping,通过HandlerMapping获取到HandlerExecutionChain

org.springframework.web.servlet.DispatcherServlet#getHandler在这里插入图片描述
实际处理HttpRequestHandler接口的HandlerMappingBeanNameUrlHandlerMapping,所以我们直接进入到其getHandler()方法:
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
在这里插入图片描述
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
在这里插入图片描述
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler
在这里插入图片描述
从上面可以看到,AbstractUrlHandlerMapping是从当前持有的handlerMap集合中根据url来查找匹配的handler对象,所以handlerMap需要事先就要注册进去;这里就要弄清楚handlerMap是在什么时候将url和handler注册到其中的:
AbstractUrlHandlerMapping继承了AbstractDetectingUrlHandlerMapping类,又因为AbstractDetectingUrlHandlerMapping继承了WebApplicationObjectSupport类,WebApplicationObjectSupport类又实现了ServletContextAware接口,又BeanNameUrlHandlerMapping类继承AbstractUrlHandlerMapping类;在Spring容器启动时,会自动默认加载BeanNameUrlHandlerMapping类,所以在实例化BeanNameUrlHandlerMapping并属性装配后,会执行其WebApplicationObjectSupport#setServletContext()方法,然后调用AbstractDetectingUrlHandlerMapping#initApplicationContext()然后在该方法中调用detectHandlers()方法,该方法扫描当前容器中所有BeanName带有"/"开头的Bean对象,将其注册到handlerMap集合中。

如下流程将请求与handler的映射关系注册到handlerMap
容器启动->实例化BeanNameUrlHandlerMapping->WebApplicationObjectSupport#setServletContext()->AbstractDetectingUrlHandlerMapping#initApplicationContext()->AbstractDetectingUrlHandlerMapping#detectHandlers()->AbstractUrlHandlerMapping#registerHandler()


当根据request从BeanNameUrlHandlerMapping中获取到了HandlerExecutionChain之后,接着根据handler来获取HandlerAdapter
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
在这里插入图片描述
用来处理HttpRequestHandler接口的适配器为HttpRequestHandlerAdapter:
在这里插入图片描述
上面的HttpRequestHandlerAdapter#supports()可以看出如果handler是HttpRequestHandler就返回true


找到了HandlerAdapter之后,就是执行通过HandlerAdapter执行handler了:
在这里插入图片描述
在这里插入图片描述
从上面可以看到,HttpRequestHandlerAdapter直接调用了HttpRequestHandlerhandleRequest()方法。


至此,我们通过源码分析了SpringMVC对HttpRequestHandler的关键处理流程,从接受到请求到找到与之匹配的HandlerMapping,以及通过支持处理的HttpRequestHandlerAdapter来调用执行HttpRequestHandler的所有流程。
并且在源码分析过程中,描述了容器启动时如何扫描到HttpRequestHandler并将其与请求映射注册到BeanNameUrlHandlerMapping中的。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring MVC 是一个基于 Servlet API 构建的 Web 框架,它是 Spring Framework 的一部分,提供了 Model-View-Controller(MVC)架构模式的实现。Spring MVC 的核心思想是将应用程序的业务逻辑与视图分离,使应用程序更容易维护和扩展。 Spring MVC 的源码涉及到很多模块,比如 DispatcherServletHandlerMapping、HandlerAdapter、ViewResolver 等等。下面我们从 DispatcherServlet 入手,深度解析 Spring MVC 的源码。 1. DispatcherServlet DispatcherServlet 是整个 Spring MVC 框架的核心,它是一个 Servlet,所有的请求都会经过它。DispatcherServlet 的主要作用是将请求分派给相应的 HandlerMapping,然后将处理结果返回给客户端。DispatcherServlet 在初始化时会加载一些配置文件,比如 applicationContext.xml、spring-mvc.xml 等等。这些配置文件中定义了 Spring MVC 的各个组件,包括 HandlerMapping、HandlerAdapter、ViewResolver 等等。 2. HandlerMapping HandlerMapping 用来映射请求到相应的处理器,它根据请求的 URL 和其他条件来确定最终的处理器。Spring MVC 中提供了多种 HandlerMapping 实现,比如 BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、SimpleUrlHandlerMapping 等等。其中,RequestMappingHandlerMapping 是最常用的 HandlerMapping 实现,它会扫描应用程序中所有带有 @Controller 注解的类,并将其中所有带有 @RequestMapping 注解的方法注册为处理器。 3. HandlerAdapter HandlerAdapter 用来确定请求处理器的类型,并调用相应的方法来处理请求。Spring MVC 中提供了多种 HandlerAdapter 实现,比如 HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、AnnotationMethodHandlerAdapter 等等。其中,AnnotationMethodHandlerAdapter 是最常用的 HandlerAdapter 实现,它会根据方法的参数类型和返回值类型动态地确定请求处理器的类型,并调用相应的方法来处理请求。 4. ViewResolver ViewResolver 用来将逻辑视图名解析为实际的视图对象,它根据逻辑视图名和其他条件来确定最终的视图对象。Spring MVC 中提供了多种 ViewResolver 实现,比如 InternalResourceViewResolver、FreeMarkerViewResolver、VelocityViewResolver 等等。其中,InternalResourceViewResolver 是最常用的 ViewResolver 实现,它会将逻辑视图名解析为 JSP 文件名,并返回一个 InternalResourceView 对象。 5. ModelAndView ModelAndView 是 Spring MVC 中最常用的视图模型对象,它包含了视图名称和模型数据。在处理请求时,请求处理器将模型数据填充到 ModelAndView 对象中,并返回一个 ModelAndView 对象作为处理结果。DispatcherServlet 会将 ModelAndView 对象传递给 ViewResolver,ViewResolver 会使用视图名称和模型数据来渲染响应结果。 以上就是 Spring MVC 源码深度解析的简要介绍,需要注意的是,Spring MVC 的源码非常庞大,涉及到很多细节问题。如果需要深入研究 Spring MVC 的源码,需要花费大量的时间和精力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值