springmvc通过处理器映射器(HandlerMapping)和处理器适配器(HandlerAdapter)来处理http请求,当一个请求经过DispatcherServlet后,DispatcherServlet会选择一个合适的处理器映射器和处理器适配器来对请求进行处理。
通过自定义处理器映射器和处理器适配器,我们可以学习掌握springmvc是如何处理一个请求的,因此本文就通过自定义映射器和适配器,来看看springmvc是如何使用映射器和适配器来处理请求的。
直接自定义没什么意思,我们先确定一个需求,然后按照这个需求来自定义映射器和适配器。一般我们处理http请求,用的最多的就是通过springmvc提供给我们的@RequestMapping,将@RequestMapping标注在方法上,springmvc就会为我们自动找到这个方法来进行请求的处理。现在我们的需求是,所有以.myhtml结尾的GET请求,我不需要写@RequestMapping来处理请求,直接返回resources目录下的/myhtml/xxx.html文件。其中文件名由请求地址决定,比如访问localhost/abc.myhtml,就会找到resources目录下的/myhtml/abc.html进行返回。这样相当于实现一个静态资源访问,而不需要我们再在controller中写带有@RequestMapping的方法。
springmvc处理请求的核心方法是org.springframework.web.servlet.DispatcherServlet#doDispatch,为了方便,我把doDispatch方法贴在文末,这样大家可以结合doDispatch的流程来看下面的文章。
getHandler
doDispatch方法中会先调用getHandler方法获取一个合适的handler来对请示进行处理,getHandler的方法也很简单,如下。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
getHandler方法会循环所有的handlerMappings,如果某个handlerMapping返回了一个handler,就使用这个handler,所以我们首先要自定义一个handlerMapping,来返回我们自定义的handler。自定义HandlerMapping和Handler如下。
自定义HandlerMapping
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import javax.servlet.http.HttpServletRequest;
public class MyHandlerMapping extends AbstractHandlerMapping {
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String servletPath = request.getServletPath();
String method = request.getMethod();
// 如果请求是以.myhtml结尾的get请求,就返回我们自定义的hander,其他情况不处理。
if (servletPath.endsWith(".myhtml") && HttpMethod.GET.matches(method)) {
MyHandler myHandler = new MyHandler();
String[] pathSplit = servletPath.split("/");
String endPath = pathSplit[pathSplit.length - 1];
String fileName = endPath.split("\\.")[0];
// setPath用来记录我们最终要查找哪个文件进行返回
myHandler.setPath(fileName);
return myHandler;
}
return null;
}
}
自定义Handler,这里是真正处理请求的地方,我们会将url中指定路径的文件通过response写回给浏览器。
@Getter
@Setter
public class MyHandler {
private String path;
public void handle(HttpServletResponse response) throws IOException {
File targetFile;
// 根据请求路径读取classpath目录下相应的文件,没有对应的文件则返回我们提前准备好的page404.html
try {
targetFile = ResourceUtils.getFile("classpath:myhtml/" + path + ".html");
} catch (FileNotFoundException e) {
e.printStackTrace();
targetFile = ResourceUtils.getFile("classpath:myhtml/page404.html");
}
// 将读取到的文件写入response中。
InputStream inputStream = new FileInputStream(targetFile);
byte[] bytes = new byte[1024];
int len;
PrintWriter writer = response.getWriter();
while ((len = inputStream.read(bytes) )!= -1) {
writer.write(new String(Arrays.copyOf(bytes, len)));
}
// 应该在finally中关流,这里简化处理了。
inputStream.close();
}
}
getHandlerAdapter
getHandlerAdapter方法如下。可以看到spring会循环所有handlerAdapter,看看当前adapter是否支持这个handler。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
之所以通过adapter来调用handler,是因为springmvc拿到handler后,就要用这个handler去处理请求了,但是handler是我们自定义的,springmvc并不知道要怎么调用我们的handler,所以这里springmvc又调用了getHandlerAdapter,也就是springmvc会通过适配器,来间接调用handler。因为只有我们自己知道自己的handler应该怎么调用,所以我们还要自定义一个handlerAdapter给springmvc,让springmvc用我们给的handlerAdapter来间接调用我们的handler。这样做的好处是给了我们自定义handler极大的自由,我们的handler可以完全不用实现任何接口,想怎么写就怎么写,只要有个对应HandlerAdapter即可。自定义HandlerAdapter如下。核心方法就是support和handle方法,support方法中,我们判断,如果是我们自定义的handler,我们就返回true,表示我们认识这个handler,这个handler可以交由我来处理。handler方法中,我们把handler强转成自己定义的handler,然后调用handle方法处理请求即可。
public class MyHandlerAdapter implements HandlerAdapter, Ordered {
// 可以实现Ordered方法,这样当有多个adapter可用时,会使用order更小的。
private int order;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return order;
}
@Override
public boolean supports(Object handler) {
return handler instanceof MyHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MyHandler myHandler = (MyHandler) handler;
myHandler.handle(response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return 0;
}
}
配置
以上做完以后,我们要把自己定义的handlerMapping和handlerAdapter注册成spring bean,这样spring就会自动使用上我们的handlerMapping和handlerAdapter。最后记得在resources目录下新建myhtml文件夹,在里面加上自己定义的页面进行测试即可。
@Configuration
public class WebMvcConfig {
@Bean
public MyHandlerMapping myHandlerMapping() {
MyHandlerMapping myHandlerMapping = new MyHandlerMapping();
myHandlerMapping.setOrder(10);
return myHandlerMapping;
}
@Bean
public MyHandlerAdapter myHandlerAdapter() {
MyHandlerAdapter myHandlerAdapter = new MyHandlerAdapter();
myHandlerAdapter.setOrder(10);
return myHandlerAdapter;
}
}
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}