SpringBoot处理静态资源映射实现原理

本文详细解释了SpringMVC框架中的WebMvcAutoConfiguration类,介绍了如何配置静态资源、Servlet、视图和资源处理器,以及如何通过条件注解如@ConditionalOnWebApplication和@ConditionalOnMissingBean来控制其在应用中的启用情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 当容器中不存在WebMvcConfigurationSupport的时候,如果手动添加了@EnableMvc注解即失效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

    class ResourceProperties {
        // 默认的静态资源路径
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/",
                "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
        private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    }

    class WebMvcProperties {
        // servlet相关配置
        private final Servlet servlet = new Servlet();
        // view相关配置
        private final View view = new View();

        class Servlet {
            // 请求根路径
            private String path = "/";
            // 启动时机
            private int loadOnStartup = -1;
        }

        class View {
            // 视图名前缀
            private String prefix;
            // 视图名后缀
            private String suffix;
        }
    }

    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        // 添加静态资源处理器
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            // 从配置文件中获取属性
            Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
            CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            // 添加wabjar的资源映射
            // webjars: 将各种js包打包成的jar,在前后端不分离的时候好用
            // 只需要引入webjar的依赖,然后配置该资源不拦截
            if (!registry.hasMappingForPattern("/webjars/**")) {
                ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**")
                        .addResourceLocations("classpath:/META-INF/resources/webjars/")
                        .setCachePeriod(getSeconds(cachePeriod))
                        .setCacheControl(cacheControl);
                // 使用ResourceHandlerRegistrationCustomizer类型的Bean对该资源处理器自定义
                this.customizeResourceHandlerRegistration(registration);
            }
            // 获取mvc配置的属性,静态资源路径
            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            // 添加指定的静态资源路径的资源处理器
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern)
                        .addResourceLocations(this.getResourceLocations(this.resourceProperties.getStaticLocations()))
                        .setCachePeriod(this.getSeconds(cachePeriod))
                        .setCacheControl(cacheControl);
                // 使用ResourceHandlerRegistrationCustomizer类型的Bean对该资源处理器自定义
                this.customizeResourceHandlerRegistration(registration);
            }
        }
    }

}

class DelegatingWebMvcConfiguration {

    private final List<WebMvcConfigurer> configurers = new ArrayList<>();

    @Bean
    public HandlerMapping resourceHandlerMapping(
            @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
            @Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
            @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
            @Qualifier("mvcConversionService") FormattingConversionService conversionService,
            @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
        // 资源处理器注册表
        ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, contentNegotiationManager, urlPathHelper);
        // 执行WebMvcConfigurer接口的addResourceHandlers,添加资源处理器
        this.addResourceHandlers(registry);

        AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
        if (handlerMapping == null) {
            return null;
        }
        handlerMapping.setPathMatcher(pathMatcher);
        handlerMapping.setUrlPathHelper(urlPathHelper);
        // 设置拦截器
        handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
        handlerMapping.setCorsConfigurations(getCorsConfigurations());
        return handlerMapping;
    }

    // 执行WebMvcConfigurer接口的addResourceHandlers,添加资源处理器
    // 这个添加了资源处理器才会有后续的处理逻辑
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        this.configurers.addResourceHandlers(registry);
    }
}

// 资源处理器注册表
class ResourceHandlerRegistry {
    // 资源处理器
    private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();

    /**
     * 生成HandlerMapping对象
     * {@link ResourceHttpRequestHandler}
     */
    protected AbstractHandlerMapping getHandlerMapping() {
        // 如果没有资源处理器,就不处理
        if (this.registrations.isEmpty()) {
            return null;
        }
        // url与HttpRequestHandler(处理请求的Handler)的映射
        Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
        // 遍历所有的资源处理器
        for (ResourceHandlerRegistration registration : this.registrations) {
            // 获取该处理器处理的路径
            for (String pathPattern : registration.getPathPatterns()) {
                // 获取该处理器处理该请求路径的Handler
                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);
                // 执行handler的初始化方法
                handler.afterPropertiesSet();
                // 保存静态资源url -> 处理该静态资源请求的Handler
                urlMap.put(pathPattern, handler);
            }
        }
        // 注册一个HandlerMapping,专门处理静态资源的请求
        return new SimpleUrlHandlerMapping(urlMap, this.order);
    }

    // 添加资源处理器
    public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
        // 创建指定路径的资源处理器
        ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
        // 保存该处理器
        this.registrations.add(registration);
        // 返回创建的处理器
        return registration;
    }
}

// 指定路径的资源处理器
class ResourceHandlerRegistration {
    // 该资源处理器处理的请求路径
    private final String[] pathPatterns;
    // pathPatterns映射到的资源路径
    private final List<String> locationValues = new ArrayList<>();

    public ResourceHandlerRegistration(String... pathPatterns) {
        this.pathPatterns = pathPatterns;
    }

    // 获取请求的处理器HttpRequestHandler
    protected ResourceHttpRequestHandler getRequestHandler() {
        // 创建处理Resource类型的Handler
        ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
        // 如果设置了资源链,也就是配置要使用的资源解析器和转换器链
        if (this.resourceChainRegistration != null) {
            // 设置该资源链中设置资源解析器和转换器
            handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
            handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
        }
        // 设置请求路径映射到的资源路径
        handler.setLocationValues(this.locationValues);
        // 如果存在缓存控制器
        if (this.cacheControl != null) {
            handler.setCacheControl(this.cacheControl);
        }
        // 如果不存在缓存控制器,设置缓存的有效事件
        else if (this.cachePeriod != null) {
            handler.setCacheSeconds(this.cachePeriod);
        }
        // 返回处理静态资源路径的Handler
        return handler;
    }

    // 添加该处理器可以处理的资源路径
    public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
        this.locationValues.addAll(Arrays.asList(resourceLocations));
        return this;
    }

}

// 路径资源解析器
class PathResourceResolver {
    // 该解析器可以处理的资源对象
    private Resource[] allowedLocations;

    // 设置该解析器可以解析的资源对象
    public void setAllowedLocations(@Nullable Resource... locations) {
        this.allowedLocations = locations;
    }

    // 解析资源
    protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
        return this.getResource(requestPath, request, locations);
    }

    // 根据路径,从给定的所有资源中,找到符合该资源路径的资源对象
    private Resource getResource(String resourcePath, @Nullable HttpServletRequest request, List<? extends Resource> locations) {
        // 遍历给定的所有资源对象
        for (Resource location : locations) {
            // url编码
            String pathToUse = encodeIfNecessary(resourcePath, request, location);
            // 根据自定路径,校验给定的资源对象是否符合路径的条件,如果符合,返回该资源对象,否则返回null
            Resource resource = this.getResource(pathToUse, location);
            if (resource != null) {
                return resource;
            }
        }
        return null;
    }

    // 根据自定路径,校验给定的资源对象是否符合路径的条件,如果符合,返回该资源对象,否则返回null
    protected Resource getResource(String resourcePath, Resource location) throws IOException {
        // 根据路径创建一个资源对象
        Resource resource = location.createRelative(resourcePath);
        // 资源是否可读
        if (resource.isReadable()) {
            // 校验该资源对象是否可用,说白了就是判断该路径的资源是不是与给的资源一致,或者有层级关系
            if (this.checkResource(resource, location)) {
                // 如果资源可以用,直接返回该资源对象
                return resource;
            }
        }
        return null;
    }

}

/**
 * 处理静态资源请求的Hanlder
 *
 * @see {@link ResourceHandlerRegistry#getHandlerMapping()}
 */
class ResourceHttpRequestHandler {
    // 给当前handler设置的处理的资源路径
    private final List<String> locationValues = new ArrayList<>(4);
    // 给当前handler设置的处理的资源路径对应的Resource对象
    private final List<Resource> locations = new ArrayList<>(4);
    // 对Resource资源解析的解析器
    private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);

    // 处理请求
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // 通过请求获取该请求中需要方法的资源路径对应的资源对象
        Resource resource = this.getResource(request);
        // 未找到资源
        if (resource == null) {
            // 响应错误
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        // 不支持的请求方法
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            response.setHeader("Allow", getAllowHeader());
            return;
        }
        // 存在该资源的缓存,该资源没有变化,直接返回
        if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
            return;
        }
        // 预处理响应体
        this.prepareResponse(response);

        // 通过contentNegotiationStrategy查找策略确定媒体类型
        // 其中PathExtensionContentNegotiationStrategy通过路径扩展名获取媒体类型
        MediaType mediaType = this.getMediaType(request, resource);


        ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
        // 请求头存在range
        if (request.getHeader(HttpHeaders.RANGE) == null) {
            // 设置请求头
            setHeaders(response, resource, mediaType);
            // 通过资源类型消息转换器将消息写入响应体中
            this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
            return;
        }
        // 如果不存在range,则使用处理ResourceRegion类型的消息转换器将资源写入响应体
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
        List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        this.resourceRegionHttpMessageConverter.write(HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
    }

    // 通过请求获取该请求中需要方法的资源路径对应的资源对象
    protected Resource getResource(HttpServletRequest request) throws IOException {
        // 获取路径
        String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        // 解析路径
        path = processPath(path);
        // 使用资源解析器,将设置的资源路径解析成Resource对象
        Resource resource = this.resolverChain.resolveResource(request, path, this.locations);
        // 如果解析到了
        if (resource != null) {
            // 在通过资源转换器执行链进行加工
            resource = this.transformerChain.transform(request, resource);
        }
        return resource;
    }


    // 对象初始化
    public void afterPropertiesSet() throws Exception {
        // 解析资源路径,将路径对应的Resource获取出来
        this.resolveResourceLocations();
        // 如果不存在资源解析器
        if (this.resourceResolvers.isEmpty()) {
            // 添加一个路径的资源解析器
            this.resourceResolvers.add(new PathResourceResolver());
        }
        // 初始化路径资源解析器可以处理哪些路径的资源
        this.initAllowedLocations();

        // 初始化资源解析器执行链
        this.resolverChain = new DefaultResourceResolverChain(this.resourceResolvers);
        // 初始化资源转换器执行链
        this.transformerChain = new DefaultResourceTransformerChain(this.resolverChain, this.resourceTransformers);
        // 设置处理资源类型Resource的消息转换器
        if (this.resourceHttpMessageConverter == null) {
            // 该转换器就是将请求体中的数据解析成Resource对象,将Resource对象写入响应体
            // 静态资源文件就是这样写入到相应体中
            this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
        }
        // 处理资源类型ResourceRegion的消息转换器,和ResourceHttpMessageConverter一样,就是处理的类型为ResourceRegion
        if (this.resourceRegionHttpMessageConverter == null) {
            this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
        }
        // 解析所媒体类型请求中的策略
        this.contentNegotiationStrategy = this.initContentNegotiationStrategy();
    }

    // 将请求路径中的文件扩展名解析为用于查找媒体类型的键
    protected PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() {
        // 获取媒体类型
        Map<String, MediaType> mediaTypes = null;
        // 如果存在管理器
        if (this.contentNegotiationManager != null) {
            // 通过管理器获取处理媒体类型的策略,PathExtensionContentNegotiationStrategy通过路径扩展名获取媒体类型
            PathExtensionContentNegotiationStrategy strategy = this.contentNegotiationManager.getStrategy(PathExtensionContentNegotiationStrategy.class);
            // 如果找到合适的策略,则获取该策略支持的媒体类型
            if (strategy != null) {
                mediaTypes = new HashMap<>(strategy.getMediaTypes());
            }
        }
        // 如果存在ServeltContext,返回ServletPathExtensionContentNegotiationStrategy,否则返回PathExtensionContentNegotiationStrategy
        // ServletPathExtensionContentNegotiationStrategy继承PathExtensionContentNegotiationStrategy,都是通过路径的扩展名
        return (getServletContext() != null ?
                new ServletPathExtensionContentNegotiationStrategy(getServletContext(), mediaTypes) :
                new PathExtensionContentNegotiationStrategy(mediaTypes));
    }

    // 初始化路径资源解析器可以处理哪些路径的资源
    protected void initAllowedLocations() {
        // 如果该HttpRequestHandler没有可处理的资源
        if (CollectionUtils.isEmpty(this.locations)) {
            return;
        }
        // 遍历所有的资源解析器
        for (int i = this.resourceResolvers.size() - 1; i >= 0; i--) {
            // 如果资源解析器为路径资源解析器
            if (this.resourceResolvers.get(i) instanceof PathResourceResolver) {
                PathResourceResolver pathResolver = (PathResourceResolver) this.resourceResolvers.get(i);
                // 获取该资源解析器支持的路径,如果为空
                if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
                    // 将当前HttpRequestHandler保存的资源设置为该路径解析器可以处理的路径
                    pathResolver.setAllowedLocations(this.locations.toArray(new Resource[0]));
                }
                // 设置字符集合url工具类
                if (this.urlPathHelper != null) {
                    pathResolver.setLocationCharsets(this.locationCharsets);
                    pathResolver.setUrlPathHelper(this.urlPathHelper);
                }
                break;
            }
        }
    }

    // 解析资源路径,将路径对应的Resource获取出来
    private void resolveResourceLocations() {
        // 获取上下文对象
        ApplicationContext applicationContext = obtainApplicationContext();
        // 遍历设置的资源路径
        for (String location : this.locationValues) {
            // 使用占位符解析路径
            if (this.embeddedValueResolver != null) {
                String resolvedLocation = this.embeddedValueResolver.resolveStringValue(location);
                location = resolvedLocation;
            }
            Charset charset = null;
            location = location.trim();
            // 资源路径是不是设置了"[charset="字符集
            if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) {
                int endIndex = location.indexOf(']', URL_RESOURCE_CHARSET_PREFIX.length());
                String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex);
                // 设置字符编码
                charset = Charset.forName(value);
                location = location.substring(endIndex + 1);
            }
            // 根据路径获取实际的资源
            Resource resource = applicationContext.getResource(location);
            // 保存该资源对象
            this.locations.add(resource);
            // 保存字符集对象
            if (charset != null) {
                this.locationCharsets.put(resource, charset);
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值