• springmvc静态资源配置


      
        dispatcher
        org.springframework.web.servlet.DispatcherServlet
        false
      
    
      
        dispatcher
        /
      

    在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.
    	 * 

    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. *

    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 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 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 uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map 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 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.
     * 

    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 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映射 

    原理可以参考上一篇文章 

      
        default
        /static/*
      

    将带有/static/xxx 路径的请求直接交给tomcat默认的servlet去进行处理 

     

    最后

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

     

  • 相关阅读:
    ArcGIS Pro、ChatGPT、Python、InVEST等多技术融合的水文、生态、气候变化等地学领域科研及项目综合能力提升
    Vue3手写分页器
    pycharm 用MD文件制作流程图
    I.MX6U-驱动开发-4-linux设备树
    八股文之JVM
    Python—— 模块和包、以及导入模块和包
    指针和数组试题解析(1)一维数组部分
    【Python百日进阶-数据分析】Day122 - Plotly Figure参数: 散点图(四)
    position left设置居中,除了auto以外,还有什么方式
    【U8+】用友U8+客户端登录账套的时候, 提示: 已成功与服务器建立连接,但是在登录过程中发生错误; 指定的网络名不再可用。
  • 原文地址:https://www.cnblogs.com/monianxd/p/16576606.html