介绍
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请求;
它们的不同在于:
HttpRequestHandler
的handlerReqeust()
不提供返回值,而Controller
接口需要返回一个ModelAndView
。- 由于
HttpRequestHandler
接口不提供返回值,所以其定位处理的请求不提供视图;Controller
接口需要提供ModelAndView
,所以需要呈现一个视图进行相应。 HttpRequestHandler
与HttpRequestHandlerServlet
处于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
- 处理
HttpRequestHandler
的HandlerAdapter
->HttpRequestHandlerAdapter
- 处理
HttpRequestHandler
的HandlerMapping
->BeanNameUrlHandlerMapping
首先是请求的入口:
org.springframework.web.servlet.DispatcherServlet#doService
org.springframework.web.servlet.DispatcherServlet#doDispatch
上面是SpringMVC的主要入口逻辑。
接下来是通过请求来获取HandlerMapping
,通过HandlerMapping
获取到HandlerExecutionChain
org.springframework.web.servlet.DispatcherServlet#getHandler
实际处理HttpRequestHandler
接口的HandlerMapping
是BeanNameUrlHandlerMapping
,所以我们直接进入到其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
直接调用了HttpRequestHandler
的handleRequest()
方法。
至此,我们通过源码分析了SpringMVC对HttpRequestHandler
的关键处理流程,从接受到请求到找到与之匹配的HandlerMapping
,以及通过支持处理的HttpRequestHandlerAdapter
来调用执行HttpRequestHandler
的所有流程。
并且在源码分析过程中,描述了容器启动时如何扫描到HttpRequestHandler
并将其与请求映射注册到BeanNameUrlHandlerMapping
中的。