我们知道,当想要请求一个资源文件,而不是跳到controller中匹配映射时,我们需要在springmvc的xml文件中设置<mvc:resources/>这个标签。
本文将以<mvc:resources mapping="/images/**" location="file:///D:/images/"/>为例,来讲解springmvc对于资源文件的执行过程。
比如通过上面的设置,当我们在页面上通过http://localhost:8088/CipSystem/images/image/20200422/20200422091811_670.png来请求一个图片时,springmvc会到D:/images/下去找对应的图片进行显示。而不会跳转到controller中去进行映射,那么代码的处理过程如何呢,如下:
当请求发送时,实现会被ResourceHttpRequestHandler的handleRequest方法处理,如下:
public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, InitializingBean, CorsConfigurationSource {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.trace("No matching resource found - returning 404");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return;
}
// Supported methods and required session
checkRequest(request);
// 根据我们图片的请求,来设置一个图片的响应,如:
if (new ServletWebRequest(
request,response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified - returning 304");
return;
}
// Apply cache settings, if any
prepareResponse(response);
// 根据请求的head,来设置响应的类型,如这里将响应的head设置image/png,
// 表示将返回一个png图片
MediaType mediaType = getMediaType(request, resource);
// Content phase
if (METHOD_HEAD.equals(request.getMethod())) {
setHeaders(response, resource, mediaType);
logger.trace("HEAD request - skipping content");
return;
}
ServletServerHttpResponse outputMessage = new
ServletServerHttpResponse(response);
// 将我们的图片作为响应返回到页面中
if (request.getHeader(HttpHeaders.RANGE) == null) {
// 设置response中head的内容,比如Content-Length,ContentType
setHeaders(response, resource, mediaType);
// 通过流的形式将图片输出到页面上
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
else {
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
ServletServerHttpRequest inputMessage = new
ServletServerHttpRequest(request);
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
if (httpRanges.size() == 1) {
ResourceRegion resourceRegion = httpRanges.get(0).
toResourceRegion(resource);
this.resourceRegionHttpMessageConverter.write(
resourceRegion, mediaType, outputMessage);
} else {
this.resourceRegionHttpMessageConverter.write(
HttpRange.toResourceRegions(httpRanges, resource),
mediaType, outputMessage);
} catch (IllegalArgumentException ex) {
response.setHeader("Content-Range", "bytes */" +
resource.contentLength());
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
}
}
}
protected Resource getResource(HttpServletRequest request) throws IOException {
// 从请求中后去请求资源的path ,如image/20200422/20200422091811_670.png
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) {
throw new IllegalStateException(
"Required request attribute '" +
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE +
"' is not set");
}
// 对path进行处理,将连续的/变为一个/
path = processPath(path);
if (!StringUtils.hasText(path) || isInvalidPath(path)) {
return null;
}
if (path.contains("%")) {
try {
// Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
if (isInvalidPath(URLDecoder.decode(path, "UTF-8"))) {
return null;
}
}catch (IllegalArgumentException ex) {
// ignore
}
}
ResourceResolverChain resolveChain = new
DefaultResourceResolverChain(getResourceResolvers());
// 根据path和xml中配置的location通过PathResourceResolver类来成Resource,代码如下
Resource resource = resolveChain.resolveResource(request, path, getLocations());
if (resource == null || getResourceTransformers().isEmpty()) {
return resource;
}
ResourceTransformerChain transformChain =
new DefaultResourceTransformerChain(
resolveChain, getResourceTransformers());
resource = transformChain.transform(request, resource);
return resource;
}
}
Resource是如何生成的呢?在调用Resource resource = resolveChain.resolveResource(request, path, getLocations());时,世界上是最终调用了PathResourceResolver的resolveResourceInternal方法,如下:
public class PathResourceResolver extends AbstractResourceResolver {
private Resource[] allowedLocations;
@Override
protected Resource resolveResourceInternal(HttpServletRequest request,
String requestPath,
List<? extends Resource> locations,
ResourceResolverChain chain) {
return getResource(requestPath, locations);
}
/**
*
*/
private Resource getResource(String resourcePath, List<? extends Resource> locations) {
for (Resource location : locations) {
try {
if (logger.isTraceEnabled()) {
logger.trace("Checking location: " + location);
}
Resource resource = getResource(resourcePath, location);
catch (IOException ex) {}
}
return null;
}
/**
*
*/
protected Resource getResource(String resourcePath, Resource location) throws IOException {
// 通过url(我们的location指定的file:/D:/images/)和
// resourcePath(image/20200422/20200422091811_670.png)生成一个UrlResource对象
// Resource resource = location.createRelative(resourcePath);
// resource.exists():判断是不是file协议,如果是将new一个file,然后通过
// AbstractFileResolvingResource的exists和isReadable方法判断这个是否存在是否可读。
if (resource.exists() && resource.isReadable()) {
// 判断resource是否合法
if (checkResource(resource, location)) {
return resource;
}else if (logger.isTraceEnabled()) {}
return null;
}
}
上面基本上就是关于设置<mvc:resource/>后,程序内部执行的核心逻辑。关于springboot内部是否是这样的,暂时未知。
总结:
还是以<mvc:resources mapping="/images/**" location="file:///D:/images/"/>为例,这个location到底可以写什么呢?看下图