springMVC是流行的web框架,它不仅轻量灵活还提供了一系列可扩展的功能,这次我们分析一个http上传请求在spring框架中的处理源码,其实本博客更多是博主自学总结使用,上篇spring源码的博文被推送到首页让博主手从若惊,一方面为自己的努力得到认可兴奋,另一方面则是担忧自己学识浅薄怕误人子弟,分析不一定面面俱到,因此我给自己的规定是尽量到自己不确定的细节不去猜测而更多客观描述,把一个生动具体的框架设计思想呈现在大家面前。
- 容器加载补充
结合上一篇文章不难理解整个springMVC的容器加载过程,也大概描述了一下http请求的处理与响应过程,朋友也提示我可以深入的分析下spring请求的处理细节,其实这个过程核心就是两个接口和三一个类:HandlerMapping HandlerAdapter接口和DispacherServlet类。
DispacherServlet在容器看来与普通的servlet并无太多差异,这个前置servlet接受所有请求,早期的servlet可能每个请求url都要去配置不同的servlet,在springMVC中这个过程交给了spring容器去管理。这里我想在容器启动分析补充springMVC加载springweb容器前会去查询是否有已存在spring容器,并会把它设置成自己父容器。在创建WebApplicationContext过程中有这么一段逻辑:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
// parent即是spring已加载容器
// 省略部分代码
// 这里设置为父容器
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
这里两个容器的bean混合使用,这里可能有一些坑,例如我之前写个controller的拦截器,但是始终无法被动态代理,后来发现拦截器bean是父容器管理,controller是子容器管理,这样是拦截器是无法感知controller的存在。因此不能完成代理功能。这里spring文档官方建议是把所有bean定义在一个容器里。
- http上传文件请求
进入这次的正题,文件上传源码分析,首先还是从DisapcherServlet分析入手,请求会被传递到doService,这里会做一些请求上下文放到当前request的属性里,然后会传递到springMVC主要请求的核心方法doDispacherServlet:
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);
// 待续
文件上传请求会被转化成springMVC支持的请求类型org.springframework.web.multipart.MultipartHttpServletRequest,这个请求的继承关系如下:
public interface MultipartHttpServletRequest extends HttpServletRequest, MultipartRequest {
HttpMethod getRequestMethod();
HttpHeaders getRequestHeaders();
HttpHeaders getMultipartHeaders(String paramOrFileName);
}
它继承(扩充)了两个接口的功能。HttpServletRequest是servlet封装的http请求request,还有个是springMVC封装的文件请求接口MultipartRequest。我们看是如何转换的checkMultipart(request);函数:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 这里要找到对应的multipartResolver
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else {
return this.multipartResolver.resolveMultipart(request);
}
}
// 如果不是multiPartRequest,返回原request
return request;
}
org.springframework.web.multipart.MultipartResolver是解析MultipartHttpServletRequest的接口,因为实现MultipartHttpServletRequest的具体类很多,或则为了更好的自己去选择,但是接口的使用者只关心获取到一个MultipartHttpServletRequest,因此不同的实现类会有不同的MultipartResolver.而我们选择不同的实现只要配置不同的MultipartResolver即可。例如我引入了file-upload.jar包会有这两个MultipartHttpServletRequest实现:
向对象也有两个解析的MultipartResolver:
我们可以看看CommonsMultipartResovler如何解析成DefaultMultipartHttpServletRequest:
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new DefaultMultipartHttpServletRequest(request) {
@Override
protected void initializeMultipart() {
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
}
};
}
else {
MultipartParsingResult parsingResult = parseRequest(request);
return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}
}
同理StandardMultipartRequestResovler解析成StandardMultipartHttpServletRequest的MultipartHttpServletRequest。这种设计思想是不是跟HandlerMapping和HandlerAdapter非常像,是的,类似于工厂模式,同理还有Spring是异常处理等等核心接口都是遵循这样一个设计思想。
我们再次回到doDispacherServlet:
// 这里获取mapping到的handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 调用对应的adapter,dispacher“只认识“HandlerAdapter,“不认识“Handler
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理请求头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// handlerAdapter处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
在控制器里这样即可获取到请求:
@RequestMapping(value = "/upload",produces = "application/*")
public void uploadFile(@RequestParam("file") MultipartFile file)
@RequestParam会把请求的MultipartHttpServletRequest解析成对应的MultipartFile参数。这个部分的源码属于参数绑定模块。这块内容之后博文会分析。