SpringMVC靜態(tài)資源配置過程詳解
springmvc靜態(tài)資源配置
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <async-supported>false</async-supported> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
在javaweb項目中配置了DispatcherServlet的情況下,如果不進(jìn)行額外配置的話,幾乎所有的請求都會走這個servlet來處理,默認(rèn)靜態(tài)資源按路徑是訪問不到的會報404錯誤,下面講一講如何配置才能訪問到靜態(tài)資源,本文將介紹三種方法
1. 在java配置文件中配置DefaultServletHttpRequestHandler來進(jìn)行處理
@Configuration @EnableWebMvc public class MyMvcConfigurer implements WebMvcConfigurer { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { // tomcat默認(rèn)處理靜態(tài)資源的servlet名稱為default,不指定也可以DefaultServletHttpRequestHandler.setServletContext會自動獲取 // configurer.enable("default"); configurer.enable(); } }
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping
方法會生成一個類名為SimpleUrlHandlerMapping
的bean,當(dāng)其他handlerMapping無法處理請求時會接著調(diào)用SimpleUrlHandlerMapping
對象進(jìn)行處理
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping /** * Return a handler mapping ordered at Integer.MAX_VALUE with a mapped * default servlet handler. To configure "default" Servlet handling, * override {@link #configureDefaultServletHandling}. */ @Bean public HandlerMapping defaultServletHandlerMapping() { Assert.state(this.servletContext != null, "No ServletContext set"); DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(this.servletContext); configureDefaultServletHandling(configurer); HandlerMapping handlerMapping = configurer.buildHandlerMapping(); return (handlerMapping != null ? handlerMapping : new EmptyHandlerMapping()); } org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer#buildHandlerMapping @Nullable protected SimpleUrlHandlerMapping buildHandlerMapping() { if (this.handler == null) { return null; } SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setUrlMap(Collections.singletonMap("/**", this.handler)); handlerMapping.setOrder(Integer.MAX_VALUE); return handlerMapping; }
SimpleUrlHandlerMapping中有一個urlMap屬性,key為請求路徑匹配模式串,'/**'能匹配所有的路徑, value為handler匹配完成后會調(diào)用handler處理請求
下面這個方法主要用來匹配獲取handler
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
? @Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 請求靜態(tài)資源 path=/zxq/static/login.png // 處理完lookupPath=/static/login.png String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); Object handler = lookupHandler(lookupPath, request); if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if ("/".equals(lookupPath)) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } if (handler != null && logger.isDebugEnabled()) { logger.debug("Mapping [" + lookupPath + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { logger.trace("No handler mapping found for [" + lookupPath + "]"); } return handler; }
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler
? /** * Look up a handler instance for the given URL path. * <p>Supports direct matches, e.g. a registered "/test" matches "/test", * and various Ant-style pattern matches, e.g. a registered "/t*" matches * both "/test" and "/team". For details, see the AntPathMatcher class. * <p>Looks for the most exact pattern, where most exact is defined as * the longest path pattern. * @param urlPath the URL the bean is mapped to * @param request current HTTP request (to expose the path within the mapping to) * @return the associated handler instance, or {@code null} if not found * @see #exposePathWithinMapping * @see org.springframework.util.AntPathMatcher */ @Nullable protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { // Direct match? // 精確匹配,是否有符合的handler // urlPath = /static/login.png Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } // Pattern match? // 路徑匹配 List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, urlPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); if (logger.isDebugEnabled()) { logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns); } bestMatch = matchingPatterns.get(0); } // bestMatch = /static/** if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); // login.png String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } if (logger.isDebugEnabled()) { logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables); } // /static/** login.png return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; }
接著調(diào)用DefaultServletHttpRequestHandler的handleRequest方法處理請求,邏輯比較簡單,獲取請求轉(zhuǎn)發(fā)器進(jìn)行請求轉(zhuǎn)發(fā)交給tomcat默認(rèn)的servlet來進(jìn)行處理
@Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Assert.state(this.servletContext != null, "No ServletContext set"); RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName); if (rd == null) { throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName + "'"); } rd.forward(request, response); }
2. 在java配置文件中配置ResourceHttpRequestHandler來進(jìn)行處理
@Configuration @EnableWebMvc public class MyMvcConfigurer implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } }
和第一種配置幾乎一樣,其實只是換了一個handler類型來處理請求罷了
上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping
方法會生成一個類名為SimpleUrlHandlerMapping
的bean,當(dāng)其他handlerMapping無法處理請求時會接著調(diào)用SimpleUrlHandlerMapping
對象進(jìn)行處理
ResourceHttpRequestHandler比DefaultServletHttpRequestHandler的構(gòu)建稍微復(fù)雜一點
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#resourceHandlerMapping /** * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped * resource handlers. To configure resource handling, override * {@link #addResourceHandlers}. */ @Bean public HandlerMapping resourceHandlerMapping() { Assert.state(this.applicationContext != null, "No ApplicationContext set"); Assert.state(this.servletContext != null, "No ServletContext set"); ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper()); addResourceHandlers(registry); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); if (handlerMapping != null) { handlerMapping.setPathMatcher(mvcPathMatcher()); handlerMapping.setUrlPathHelper(mvcUrlPathHelper()); handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setCorsConfigurations(getCorsConfigurations()); } else { handlerMapping = new EmptyHandlerMapping(); } return handlerMapping; } org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry#getHandlerMapping /** * Return a handler mapping with the mapped resource handlers; or {@code null} in case * of no registrations. */ @Nullable protected AbstractHandlerMapping getHandlerMapping() { if (this.registrations.isEmpty()) { return null; } Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>(); for (ResourceHandlerRegistration registration : this.registrations) { for (String pathPattern : registration.getPathPatterns()) { ResourceHttpRequestHandler handler = registration.getRequestHandler(); if (this.pathHelper != null) { handler.setUrlPathHelper(this.pathHelper); } if (this.contentNegotiationManager != null) { handler.setContentNegotiationManager(this.contentNegotiationManager); } handler.setServletContext(this.servletContext); handler.setApplicationContext(this.applicationContext); try { handler.afterPropertiesSet(); } catch (Throwable ex) { throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex); } urlMap.put(pathPattern, handler); } } SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); handlerMapping.setOrder(order); handlerMapping.setUrlMap(urlMap); return handlerMapping; }
之后也是調(diào)用SimpleUrlHandlerMapping相同的邏輯先根據(jù)請求路徑匹配找到對應(yīng)處理的handler,這里對應(yīng)的是ResourceHttpRequestHandler之后調(diào)用handleRequest方法,原理是先根據(jù)請求的路徑找到對應(yīng)的資源文件,再獲取資源文件的輸入流寫入到response響應(yīng)中,源碼如下:
org.springframework.web.servlet.resource.ResourceHttpRequestHandler#handleRequest
?/** * Processes a resource request. * <p>Checks for the existence of the requested resource in the configured list of locations. * If the resource does not exist, a {@code 404} response will be returned to the client. * If the resource exists, the request will be checked for the presence of the * {@code Last-Modified} header, and its value will be compared against the last-modified * timestamp of the given resource, returning a {@code 304} status code if the * {@code Last-Modified} value is greater. If the resource is newer than the * {@code Last-Modified} value, or the header is not present, the content resource * of the resource will be written to the response with caching headers * set to expire one year in the future. */ @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // For very general mappings (e.g. "/") we need to check 404 first // 根據(jù)請求的文件路徑找到對應(yīng)的資源文件 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 // 校驗支持的方法GET和HEAD 以及驗證session是否必須 checkRequest(request); // Header phase if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) { logger.trace("Resource not modified - returning 304"); return; } // Apply cache settings, if any // 可以根據(jù)設(shè)置的秒數(shù)設(shè)置緩存時間 cache-control:max-age=xxx prepareResponse(response); // Check the media type for the resource // 根據(jù)文件后綴去尋找 png -> image/png MediaType mediaType = getMediaType(request, resource); if (mediaType != null) { if (logger.isTraceEnabled()) { logger.trace("Determined media type '" + mediaType + "' for " + resource); } } else { if (logger.isTraceEnabled()) { logger.trace("No media type found for " + resource + " - not sending a content-type header"); } } // 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) { Assert.state(this.resourceHttpMessageConverter != null, "Not initialized"); // 設(shè)置content-type、content-length等響應(yīng)頭 setHeaders(response, resource, mediaType); // 將文件流寫入到response響應(yīng)中 this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage); } else { Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized"); response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request); try { List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); 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); } } }
3. web.xml配置servlet映射
原理可以參考上一篇文章
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/static/*</url-pattern> </servlet-mapping>
將帶有/static/xxx 路徑的請求直接交給tomcat默認(rèn)的servlet去進(jìn)行處理
最后
完成上述的一種配置后就能訪問到我們的靜態(tài)資源了,請求路徑http://localhost:8082/zxq/static/login.png
補(bǔ)充
若配置了攔截器且使用第二種方法,攔截器也會對靜態(tài)資源進(jìn)行攔截,若不需要攔截還需要進(jìn)行額外的配置去除比較麻煩
第一種方法用dispatcherServlet攔截所有的請求再將請求交給tomcat默認(rèn)的servlet處理,性能上有所消耗,攔截器不過濾
第二種方法攔截器會進(jìn)行過濾若需要過濾的路徑較多配置麻煩
第三種方法直接用tomcat默認(rèn)的servlet進(jìn)行處理,但靜態(tài)資源路徑有多個時配置也比較麻煩
綜上所述,根據(jù)自己項目的情況選擇哪一種方法~
@Configuration @EnableWebMvc public class MyMvcConfigurer implements WebMvcConfigurer { @Resource private CustomInterceptor customInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(customInterceptor) .addPathPatterns("/**") .excludePathPatterns("/static/**"); } // @Override // public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { // // tomcat默認(rèn)處理靜態(tài)資源的servlet名稱為default,不指定也可以DefaultServletHttpRequestHandler.setServletContext會自動獲取 //// configurer.enable("default"); // configurer.enable(); // } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } }
以上就是SpringMVC靜態(tài)資源配置過程詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringMVC靜態(tài)資源配置的資料請關(guān)注腳本之家其它相關(guān)文章!
- 聊聊SpringMVC項目依賴和靜態(tài)資源導(dǎo)出問題
- Java SpringMVC 集成靜態(tài)資源的方式你了解嗎
- SpringMVC靜態(tài)資源訪問問題如何解決
- SpringMvc靜態(tài)資源訪問實現(xiàn)方法代碼實例
- SpringMVC訪問靜態(tài)資源的三種方式小結(jié)
- SpringMVC訪問靜態(tài)資源的方法
- 詳解SpringMVC中設(shè)置靜態(tài)資源不被攔截的問題
- 詳解springmvc攔截器攔截靜態(tài)資源
- SpringMVC 攔截器不攔截靜態(tài)資源的三種處理方式方法
- SpringMvc配置靜態(tài)資源訪問路徑的實現(xiàn)
相關(guān)文章
Java編程刪除鏈表中重復(fù)的節(jié)點問題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復(fù)的節(jié)點問題解決思路及源碼分享,具有一定參考價值,這里分享給大家,供需要的朋友了解。2017-10-10解決異常處理問題:getReader()?has?already?been?called?for?this
這篇文章主要介紹了解決異常處理:getReader()?has?already?been?called?for?this問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01SpringMVC MVC架構(gòu)與Servlet使用詳解
MVC設(shè)計模式一般指 MVC 框架,M(Model)指數(shù)據(jù)模型層,V(View)指視圖層,C(Controller)指控制層。使用 MVC 的目的是將 M 和 V 的實現(xiàn)代碼分離,使同一個程序可以有不同的表現(xiàn)形式。其中,View 的定義比較清晰,就是用戶界面2022-10-10Spring?Boot如何在加載bean時優(yōu)先選擇我
這篇文章主要介紹了Spring?Boot如何在加載bean時優(yōu)先選擇我,在?Spring?Boot?應(yīng)用程序中,我們可以采取三種方式實現(xiàn)自己的?bean?優(yōu)先加載,本文通過實例代碼給大家詳細(xì)講解,需要的朋友可以參考下2023-03-03spring boot 項目中使用thymeleaf模板的案例分析
這篇文章主要介紹了spring boot 項目中使用thymeleaf模板的案例分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Spring security 如何開放 Swagger 訪問權(quán)限
這篇文章主要介紹了Spring security 如何開放 Swagger 訪問權(quán)限操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09