在spring boot 1.5.x中,resources/static目录下的静态资源可以直接访问,并且访问路径上不用带static,比如静态资源放置位置如下图所示:
那么访问静态资源的路径可以是:
当有配置自定义HandlerInterceptor拦截器时,请求以上静态资源路径不会被拦截。自定义HandlerInterceptor拦截器源码如下:
package com.itopener.demo.config; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; public class LoginRequiredInterceptor extends HandlerInterceptorAdapter { private final Logger logger = LoggerFactory.getLogger(LoginRequiredInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info(request.getRequestURI()); return super.preHandle(request, response, handler); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info(request.getRequestURI()); super.afterCompletion(request, response, handler, ex); } }
配置如下:
package com.itopener.demo.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfiguration extends WebMvcConfigurerAdapter { private final Logger logger = LoggerFactory.getLogger(WebMvcConfiguration.class); @Override public void addInterceptors(InterceptorRegistry registry) { logger.info("add interceptors"); registry.addInterceptor(new LoginRequiredInterceptor()); } }
访问静态资源时路径上不用加static目录:
当spring boot版本升级为2.x时,访问静态资源就会被HandlerInterceptor拦截
这样对于利用HandlerInterceptor来处理访问权限或其他相关的功能就会受影响,跟踪源码查看原因,是因为spring boot 2.x依赖的spring 5.x版本,相对于spring boot 1.5.x依赖的spring 4.3.x版本而言,针对资源的拦截器初始化时有区别,具体源码在WebMvcConfigurationSupport中,spring 4.3.x源码如下:
/** * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped * resource handlers. To configure resource handling, override * {@link #addResourceHandlers}. */ @Bean public HandlerMapping resourceHandlerMapping() { ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, mvcContentNegotiationManager()); addResourceHandlers(registry); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); if (handlerMapping != null) { handlerMapping.setPathMatcher(mvcPathMatcher()); handlerMapping.setUrlPathHelper(mvcUrlPathHelper()); // 此处固定添加了一个Interceptor handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())); handlerMapping.setCorsConfigurations(getCorsConfigurations()); } else { handlerMapping = new EmptyHandlerMapping(); } return handlerMapping; }
而spring 5.x的源码如下:
/** * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped * resource handlers. To configure resource handling, override * {@link #addResourceHandlers}. */ @Bean public HandlerMapping resourceHandlerMapping() { Assert.state(this.applicationContext != null, "No ApplicationContext set"); Assert.state(this.servletContext != null, "No ServletContext set"); ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper()); addResourceHandlers(registry); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); if (handlerMapping != null) { handlerMapping.setPathMatcher(mvcPathMatcher()); handlerMapping.setUrlPathHelper(mvcUrlPathHelper()); // 此处是将所有的HandlerInterceptor都添加了(包含自定义的HandlerInterceptor) handlerMapping.setInterceptors(getInterceptors()); handlerMapping.setCorsConfigurations(getCorsConfigurations()); } else { handlerMapping = new EmptyHandlerMapping(); } return handlerMapping; } /** * Provide access to the shared handler interceptors used to configure * {@link HandlerMapping} instances with. This method cannot be overridden, * use {@link #addInterceptors(InterceptorRegistry)} instead. */ protected final Object[] getInterceptors() { if (this.interceptors == null) { InterceptorRegistry registry = new InterceptorRegistry(); // 此处传入新new的registry对象,在配置类当中设置自定义的HandlerInterceptor后即可获取到 addInterceptors(registry); registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService())); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())); this.interceptors = registry.getInterceptors(); } return this.interceptors.toArray(); }
从源码当中可以看出,使用spring 5.x时,静态资源也会执行自定义的拦截器,因此在配置拦截器的时候需要指定排除静态资源的访问路径,即配置改为如下即可:
package com.itopener.demo.config; import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author fuwei.deng * @date 2018年4月13日 下午3:32:54 * @version 1.0.0 */ @Configuration public class WebMvcConfiguration implements WebMvcConfigurer { private final Logger logger = LoggerFactory.getLogger(WebMvcConfiguration.class); @Override public void addInterceptors(InterceptorRegistry registry) { logger.info("add interceptors"); registry.addInterceptor(new LoginRequiredInterceptor()).excludePathPatterns(Arrays.asList("/views/**", "/res/**")); } }
这样就可以和spring boot 1.5.x一样的方式使用了。不过从源码当中可以看出,每个静态资源的请求都会被自定义Interceptor拦截,只是通过访问路径判断后不会执行拦截器的内容,所以spring 5.x相对于spring 4.3.x而言,这部分处理的性能会更低一些
说明:
-
本文中测试使用的具体版本:
-
spring-boot-1.5.3.RELEASE(相对应的spring版本是spring-webmvc-4.3.8.RELEASE)
-
spring-boot-2.0.1.RELEASE(相对应的spring版本是spring-webmvc-5.0.5.RELEASE)
-
-
关于配置类,在spring boot 2.x已经改为最低支持jdk8版本,而jdk8中的接口允许有默认实现,所以已经废弃掉WebMvcConfigurerAdapter适配类,而改为直接实现WebMvcConfigurer接口