默认情况下,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个疑问:
ResourceHandlerRegistry是如何创建的 当访问这些静态资源时对应的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