显式配置 @EnableWebMvc 或 WebMvcConfigurationSupport 导致静态资源访问失败

  1. 静态资源访问失败原因

当用户在 springboot main class 上面显式使用了 @EnableWebMvc 或者为了跨域等配置了 WebMvcConfigurationSupport,

会出现放在 src/main/resources/static 目录下面的静态资源访问不到的问题。

@SpringBootApplication

@EnableWebMvc

public class DemoApplication {

   public static void main(String[] args) {

       SpringApplication.run(DemoApplication.class, args);

   }

}
@Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {}

比如在用户代码目录 src/main/resources 里有一个 hello.txt 的资源。

访问 http://localhost:8080/hello.txt 返回的结果是 404

静态资源访问失败原因

那么为什么用户显式配置了 @EnableWebMvc,spring boot访问静态资源会失败?

我们先来看下 @EnableWebMvc 的实现:

@Import(DelegatingWebMvcConfiguration.class)

public @interface EnableWebMvc {

}

/**

* A subclass of {@code WebMvcConfigurationSupport} that detects and delegates

* to all beans of type {@link WebMvcConfigurer} allowing them to customize the

* configuration provided by {@code WebMvcConfigurationSupport}. This is the

* class actually imported by {@link EnableWebMvc @EnableWebMvc}.

*

* @author Rossen Stoyanchev

* @since 3.1

*/

@Configuration

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}

可以看到 @EnableWebMvc 引入了 WebMvcConfigurationSupport,

是 spring mvc 3.1 里引入的一个自动初始化配置的 @Configuration 类。

再来看下 spring boot 里是怎么实现对 src/main/resources/static 这些目录的支持。

主要是通过 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration 来实现的。

@Configuration

@ConditionalOnWebApplication

@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)

@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })

public class WebMvcAutoConfiguration {}

可以看到 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) ,这个条件导致 spring boot 的 WebMvcAutoConfiguration不生效。

总结下具体的原因

用户配置了 @EnableWebMvc

Spring扫描所有的注解,再从注解上扫描到 @Import,把这些 @Import引入的bean信息都缓存起来

在扫描到 @EnableWebMvc时,通过 @Import 加入了 DelegatingWebMvcConfiguration,也就是 WebMvcConfigurationSupport

spring 再处理 @Conditional 相关的注解,判断发现已有 WebMvcConfigurationSupport,就跳过了spring boot 的 WebMvcAutoConfiguration

所以 spring boot 自己的静态资源配置不生效。

其实在spring boot的文档里也有提到这点: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.

在spring boot里静态资源目录的配置是在 ResourceProperties 里。

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware {

  private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {

           "classpath:/META-INF/resources/", "classpath:/resources/",

           "classpath:/static/", "classpath:/public/" 
  };

然后在 WebMvcAutoConfigurationAdapter 里会初始始化相关的 ResourceHandler。

@Configuration(
    proxyBeanMethods = false
)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
        } else {
            Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
            CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
            if (!registry.hasMappingForPattern("/webjars/**")) {
                this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }

            String staticPathPattern = this.mvcProperties.getStaticPathPattern();
            if (!registry.hasMappingForPattern(staticPathPattern)) {
                this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
            }

        }
    }
}

用户可以自己修改这个默认的静态资源目录,但是不建议,因为很容易引出奇怪的404问题。


转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:显式配置 @EnableWebMvc 或 WebMvcConfigurationSupport 导致静态资源访问失败

字数:773

本文作者:夏来风

发布时间:2020-12-27, 09:38:36

原始链接:http://www.demo1024.com/blog/java-web-resource-404/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。