深入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问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值