0
点赞
收藏
分享

微信扫一扫

springmvc自定义处理器映射器和处理器适配器

荷一居茶生活 2022-01-13 阅读 67

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)));
        }
    }
}

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);
				}
			}
		}
	}
举报

相关推荐

0 条评论