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

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

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring MVC 适配器(HandlerAdapter)是 Spring MVC 框架中的一个关键组件。它的主要作用是将不同类型的处理器(Handler)与请求进行适配,以便能够处理客户端请求并返回响应。 Spring MVC 框架支持多种类型的处理器,包括带注解的控制器(如 @Controller)、实现了特定接口的处理器(如 HttpRequestHandler、Callable)等。不同类型的处理器对应不同的处理方式,而适配器就负责将请求转发给正确的处理器,并将处理结果包装成 ModelAndView 对象返回给前端。 适配器的工作流程如下: 1. 请求到达 DispatcherServlet。 2. DispatcherServlet 根据请求的 URL 找到对应的 HandlerMapping。 3. HandlerMapping 根据 URL 映射关系找到具体的 Handler(处理器)。 4. 适配器根据 Handler 的类型选择合适的适配器进行处理。 5. 适配器调用 Handler 的方法进行实际处理,并获取处理结果。 6. 适配器将处理结果封装成 ModelAndView 对象,并返回给 DispatcherServlet。 7. DispatcherServlet 根据 ModelAndView 对象渲染视图并返回给客户端。 Spring MVC 框架提供了多个适配器实现类,默认情况下会根据处理器的类型选择对应的适配器。如果需要自定义适配器,可以实现 HandlerAdapter 接口并注册到 Spring 容器中。 总的来说,Spring MVC 适配器起到了桥接的作用,将不同类型的处理器与请求进行适配,实现了灵活的请求处理和响应返回。这种设计模式使得 Spring MVC 能够支持多样化的处理器类型,并提供了可扩展性和灵活性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值