文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringBoot静态资源配置原理详解

2024-11-30 15:50

关注

默认情况下,Spring Boot从类路径中的/static(或/public或/resources或/META-INF/resources)目录或ServletContext的根目录中提供静态内容。它使用来自Spring MVC的ResourceHttpRequestHandler,因此可以通过添加自己的WebMvcConfigurer并覆盖addResourceHandlers方法来修改该行为。

默认情况下,资源映射在/**上,但是你可以使用spring.mvc.static-path-pattern配置属性进行修改。例如,将所有资源重新定位到/resources/**可以实现如下:

默认静态资源路径

spring:
web:
resources:
static-locations:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/

目录结构如下:

默认访问路径:​​http://localhost:8080/xxx.yy​​

修改访问路径​

spring:
mvc:
static-path-pattern: /res/**

如上修改后访问路径:
​http://localhost:8080/res/xxx.yy​​

注意:如果你使用的是旧版本Springboot,这里的静态资源配置是spring.resources.static-locations

添加静态资源路径

spring:
web:
resources:
static-locations:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
- file:///D:/images/

上面的:file:///D:/images/

编程方式配置

@Configuration
public class WebConfig implements WebMvcConfigurer {


@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("file:///d:/images/") ;
registry.addResourceHandler("/h5/**").addResourceLocations("file:///d:/h5/") ;
}

}

上面配置了2个文件系统的资源目录,分别以:/static/**,/h5/**路径进行访问

访问:
​http://localhost:8080/static/xxx.yy,http://localhost:8080/h5/xxx。​​

WebJars静态资源

除了前面提到的“标准”静态资源位置之外,Webjars内容还有一个特殊情况。任何路径在/webjars/**中的资源都是从jar文件中提供的,前提是它们以webjars格式打包的。

如果你的应用程序打包为jar,请不要使用src/main/webapp目录。尽管这个目录是一个常见的标准,但它只适用于war打包,并且如果你生成一个jar,它会被大多数构建工具默默地忽略。

Spring Boot还支持Spring MVC提供的高级资源处理功能,允许使用缓存破坏静态资源或为Webjars使用版本不可知的URL等用例。

要为Webjars使用版本不可知的url,请添加webjars-locator-core依赖项。然后声明你的webjar。以jQuery为例,添加"/webjars/jQuery/jQuery .min.js"会得到"
/webjars/jQuery/x.y.z/jQuery .min.js",其中x.y.z是webjar版本。

为了使用缓存破坏,下面的配置为所有静态资源配置缓存破坏解决方案,有效地在url中添加内容哈希,例如:​

spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"

静态资源访问原理

SpringMVC核心组件配置:​

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 注入当前环境中所有的WebMvcConfigurer类型的Bean
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
// 添加到上面的WebMvcConfigurerComposite中
this.configurers.addWebMvcConfigurers(configurers);
}
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 调用WebMvcConfigurerComposite#addResourceHandlers方法,该方法内部
// 遍历所有的WebMvcConfigurer分别调用addResourceHandlers方法
this.configurers.addResourceHandlers(registry);
}
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addResourceHandlers(registry);
}
}
}

Spring提供的一个WebMvcConfigurer实现​

public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// ...
// addResourceHandler注册资源实例
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
// getStaticPathPattern获取配置文件中spring.mvc.staticPathPattern属性值
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
// getStaticLocations获取配置文件中spring.web.resources.staticLocations属性值
// 该方法调用后就会将资源访问路径与具体资源路径进行关联
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
// 创建并获取资源访问模式的的实例
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
// 自定义配置
customizer.accept(registration);
// 缓存设置
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
customizeResourceHandlerRegistration(registration);
}
}

ResourceHandlerRegistry​

public class ResourceHandlerRegistry {
private final List registrations = new ArrayList<>();
// 为每一种资源创建ResourceHandlerRegistration实例,添加到List集合中
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
this.registrations.add(registration);
return registration;
}
}

通过上面的源码我们只看到收集容器中所有WebMvcConfigurer类型的Bean,然后分别调用重写的addResourceHandlers方法接着为每一种资源访问路径/xxx创建对应的ResourceHandlerRegistration实例,并且将这些实例添加到ResourceHandlerRegistry中。

这里有2个疑问:

  1. ResourceHandlerRegistry是如何创建的
  2. 当访问这些静态资源时对应的HandlerMapping及Adapter又是谁如何与上面的ResourceHandlerRegistration关联的。

ResourceHandlerRegistry创建

上面的
DelegatingWebMvcConfiguration配置类继承WebMvcConfigurationSupport,该父类中有如下方法:​

public class WebMvcConfigurationSupport {
// 该Bean是一个HandlerMapping(这是个接口),用来确定当前请求对应的处理器类
@Bean
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
// 这里创建了资源注册器类
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
// 添加注册静态资源,该访问正好被子类DelegatingWebMvcConfiguration重写了
// 而 在上面源码看到,子类就是遍历了容器中所有的WebMvcConfigurer对应的addResourceHandlers方法
// 到这里你就清楚了静态资源的注册入口,接下来就是这些静态资源对应是如何与HandlerMapping关联的
addResourceHandlers(registry);
// 获取HandlerMapping对象
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
// ...
return handlerMapping;
}
}

通过ResourceHandlerRegistry获取HandlerMapping对象​

public class ResourceHandlerRegistry {
protected AbstractHandlerMapping getHandlerMapping() {
// 如果没有配置静态资源,那么就没有必要注册HandlerMapping了,直接返回null
if (this.registrations.isEmpty()) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
// 遍历上面注册的所有静态资源对应的ResourceHandlerRegistration
for (ResourceHandlerRegistration registration : this.registrations) {
// 将ResourceHandlerRegistration对象转换为ResourceHttpRequestHandler对象
ResourceHttpRequestHandler handler = getRequestHandler(registration);
for (String pathPattern : registration.getPathPatterns()) {
// 以配置的访问路径为key,对应的ResourceHttpRequestHandler为处理句柄
// 当一个请求过来如果匹配了当前的模式,那么就会用对应的ResourceHttpRequestHandler对象进行处理
urlMap.put(pathPattern, handler);
}
}
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
private ResourceHttpRequestHandler getRequestHandler(ResourceHandlerRegistration registration) {
// 获取
ResourceHttpRequestHandler handler = registration.getRequestHandler();
handler.setServletContext(this.servletContext);
handler.setApplicationContext(this.applicationContext);
try {
// 执行初始化
handler.afterPropertiesSet();
}
return handler;
}
}
public class ResourceHandlerRegistration {
protected ResourceHttpRequestHandler getRequestHandler() {
// 创建对象
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
// ...
// 设置路径
handler.setLocationValues(this.locationValues);
handler.setLocations(this.locationsResources);
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
// ... 这里缓存设置
return handler;
}
}

ResourceHttpRequestHandler对应的HandlerAdapter对象​

public class HttpRequestHandlerAdapter implements HandlerAdapter {


@Override
public boolean supports(Object handler) {
// ResourceHttpRequestHandler实例HttpRequestHandler子类
return (handler instanceof HttpRequestHandler);
}


@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 直接调用ResourceHttpRequestHandler#handleRequest方法
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
}


来源:实战案例锦集内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯