深入Spring Boot:顯式配置 @EnableWebMvc 導致靜態資源訪問失敗

現象

當用戶在自己的spring boot main class上面顯式使用了 @EnableWebMvc,發現原來的放在 src/main/resources/static 目錄下面的靜態資源訪問不到了。

  1. @SpringBootApplication

  2. @EnableWebMvc

  3. public class DemoApplication {

  4.    public static void main(String[] args) {

  5.        SpringApplication.run(DemoApplication.class, args);

  6.    }

  7. }

比如在用戶代碼目錄 src/main/resources裏有一個 hello.txt的資源。訪問 http://localhost:8080/hello.txt 返回的結果是404:

  1. Whitelabel Error Page

  2. This application has no explicit mapping for /error, so you are seeing this as a fallback.

  3. Thu Jun 01 11:39:41 CST 2017

  4. There was an unexpected error (type=Not Found, status=404).

  5. No message available

靜態資源訪問失敗原因

@EnableWebMvc的實現

那麼爲什麼用戶顯式配置了 @EnableWebMvc,spring boot訪問靜態資源會失敗?

我們先來看下 @EnableWebMvc的實現:

  1. @Import(DelegatingWebMvcConfiguration.class)

  2. public @interface EnableWebMvc {

  3. }

  1. /**

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

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

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

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

  6. *

  7. * @author Rossen Stoyanchev

  8. * @since 3.1

  9. */

  10. @Configuration

  11. public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

可以看到 @EnableWebMvc 引入了 WebMvcConfigurationSupport,是spring mvc 3.1裏引入的一個自動初始化配置的 @Configuration 類。

spring boot裏的靜態資源訪問的實現

再來看下spring boot裏是怎麼實現對 src/main/resources/static這些目錄的支持。

主要是通過 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration來實現的。

  1. @Configuration

  2. @ConditionalOnWebApplication

  3. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,

  4.        WebMvcConfigurerAdapter.class })

  5. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

  6. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)

  7. @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,

  8.        ValidationAutoConfiguration.class })

  9. public class WebMvcAutoConfiguration {

可以看到 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) ,這個條件導致spring boot的 WebMvcAutoConfiguration不生效。

總結下具體的原因:

  1. 用戶配置了 @EnableWebMvc

  2. Spring掃描所有的註解,再從註解上掃描到 @Import,把這些 @Import引入的bean信息都緩存起來

  3. 在掃描到 @EnableWebMvc時,通過 @Import加入了 DelegatingWebMvcConfiguration,也就是 WebMvcConfigurationSupport

  4. spring再處理 @Conditional相關的註解,判斷髮現已有 WebMvcConfigurationSupport,就跳過了spring bootr的 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的配置

在spring boot裏靜態資源目錄的配置是在 ResourceProperties裏。

  1. @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)

  2. public class ResourceProperties implements ResourceLoaderAware {

  3.    private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };

  4.    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {

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

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

  7.    private static final String[] RESOURCE_LOCATIONS;

  8.    static {

  9.        RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length

  10.                + SERVLET_RESOURCE_LOCATIONS.length];

  11.        System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,

  12.                SERVLET_RESOURCE_LOCATIONS.length);

  13.        System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,

  14.                SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);

  15.    }

然後在 WebMvcAutoConfigurationAdapter裏會初始始化相關的ResourceHandler。

  1. //org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

  2. @Configuration

  3. @Import({ EnableWebMvcConfiguration.class, MvcValidatorRegistrar.class })

  4. @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })

  5. public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {

  6.  private static final Log logger = LogFactory

  7.      .getLog(WebMvcConfigurerAdapter.class);

  8.  private final ResourceProperties resourceProperties;

  9.  @Override

  10.  public void addResourceHandlers(ResourceHandlerRegistry registry) {

  11.    if (!this.resourceProperties.isAddMappings()) {

  12.      logger.debug("Default resource handling disabled");

  13.      return;

  14.    }

  15.    Integer cachePeriod = this.resourceProperties.getCachePeriod();

  16.    if (!registry.hasMappingForPattern("/webjars/**")) {

  17.      customizeResourceHandlerRegistration(

  18.          registry.addResourceHandler("/webjars/**")

  19.              .addResourceLocations(

  20.                  "classpath:/META-INF/resources/webjars/")

  21.          .setCachePeriod(cachePeriod));

  22.    }

  23.    String staticPathPattern = this.mvcProperties.getStaticPathPattern();

  24.    if (!registry.hasMappingForPattern(staticPathPattern)) {

  25.      customizeResourceHandlerRegistration(

  26.          registry.addResourceHandler(staticPathPattern)

  27.              .addResourceLocations(

  28.                  this.resourceProperties.getStaticLocations())

  29.          .setCachePeriod(cachePeriod));

  30.    }

  31.  }

用戶可以自己修改這個默認的靜態資源目錄,但是不建議,因爲很容易引出奇怪的404問題。

發佈了170 篇原創文章 · 獲贊 476 · 訪問量 240萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章