springmvc静态资源配置

  <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的情况下,如果不进行额外配置的话,几乎所有的请求都会走这个servlet来处理,默认静态资源按路径是访问不到的会报404错误,下面讲一讲如何配置才能访问到静态资源,本文将介绍三种方法

1. 在java配置文件中配置DefaultServletHttpRequestHandler来进行处理 

@Configuration @EnableWebMvc public class MyMvcConfigurer implements WebMvcConfigurer {    @Override   public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {     // tomcat默认处理静态资源的servlet名称为default,不指定也可以DefaultServletHttpRequestHandler.setServletContext会自动获取 //    configurer.enable("default");     configurer.enable();   } }

上述配置完成后org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#defaultServletHandlerMapping 方法会生成一个类名为SimpleUrlHandlerMapping的bean,当其他handlerMapping无法处理请求时会接着调用SimpleUrlHandlerMapping对象进行处理

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匹配完成后会调用handler处理请求 

下面这个方法主要用来匹配获取handler

org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal
 	@Override 	@Nullable 	protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 		// 请求静态资源 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; 	}

接着调用DefaultServletHttpRequestHandler的handleRequest方法处理请求,逻辑比较简单,获取请求转发器进行请求转发交给tomcat默认的servlet来进行处理 

	@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来进行处理 

@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,当其他handlerMapping无法处理请求时会接着调用SimpleUrlHandlerMapping对象进行处理

ResourceHttpRequestHandler比DefaultServletHttpRequestHandler的构建稍微复杂一点

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

 之后也是调用SimpleUrlHandlerMapping相同的逻辑先根据请求路径匹配找到对应处理的handler,这里对应的是ResourceHttpRequestHandler之后调用handleRequest方法,原理是先根据请求的路径找到对应的资源文件,再获取资源文件的输入流写入到response响应中,源码如下:

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   // 根据请求的文件路径找到对应的资源文件   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   // 可以根据设置的秒数设置缓存时间 cache-control:max-age=xxx   prepareResponse(response);    // Check the media type for the resource   // 根据文件后缀去寻找 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");     // 设置content-type、content-length等响应头     setHeaders(response, resource, mediaType);     // 将文件流写入到response响应中     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默认的servlet去进行处理 

 

最后

完成上述的一种配置后就能访问到我们的静态资源了,请求路径http://localhost:8082/zxq/static/login.png

springmvc静态资源配置

springmvc静态资源配置

 

补充

若配置了拦截器且使用第二种方法,拦截器也会对静态资源进行拦截,若不需要拦截还需要进行额外的配置去除比较麻烦

第一种方法用dispatcherServlet拦截所有的请求再将请求交给tomcat默认的servlet处理,性能上有所消耗,拦截器不过滤

第二种方法拦截器会进行过滤若需要过滤的路径较多配置麻烦 

第三种方法直接用tomcat默认的servlet进行处理,但静态资源路径有多个时配置也比较麻烦 

综上所述,根据自己项目的情况选择哪一种方法~ 

@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默认处理静态资源的servlet名称为default,不指定也可以DefaultServletHttpRequestHandler.setServletContext会自动获取 ////    configurer.enable("default"); //    configurer.enable(); //  }    @Override   public void addResourceHandlers(ResourceHandlerRegistry registry) {     registry.addResourceHandler("/static/**").addResourceLocations("/static/");   } }

 

发表评论

评论已关闭。

相关文章