我們知道,當想要請求一個資源文件,而不是跳到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到底可以寫什麼呢?看下圖