1. 此坑的背景
說到這個坑,來得挺機緣巧合的。當時老師帶着我們做springboot項目時,因爲涉及到了登錄權限的問題,所以,就帶着我們寫了個登錄攔截器。這一寫,導致我整個springboot項目中的靜態資源全都無法訪問。當時,看着老師的項目是能夠正常訪問的,所以,當我出現這個問題時,我並沒有考慮到是攔截器的原因。趁此機會,我就適當地去了解下SpringBoot對靜態資源的處理。說實話,這個問題折騰了我一個下午,這確實令人有點抓狂,好在這個問題最終被解決了。在此,記錄一下。
2. SpringBoot1.5.x
SpringBoot2.0+與SpringBoot1.5.x之間還是存在許多差別的。可以看看這篇博文------Spring Boot1.5X升級到2.0指南,說不定對你有幫助。
在SpringBoot1.5.x中,放在resources/static目錄下的靜態資源可以被直接訪問的,並且訪問路徑不需要帶上“static”。如果,你還不太清楚springboot對靜態資源的處理,可以查看這篇博文 SpringBoot對靜態資源映射的處理
即使給此版本的springboot項目添加攔截器,請求靜態資源時,也不會被攔截。因爲我比較懶,在此處就不給出相應的例子了。(下面有)
3. SpringBoot2.0+
添加攔截器:
要想實現登錄攔截,可以使用SpringMVC中的攔截器。類似於Servlet中的過濾器
@Component
public class UserLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = (User)request.getSession().getAttribute("user");
if (StringUtils.isEmpty(user)) {
response.sendRedirect("/login.html");
return false;
}
return true;
}
}
新建一個類UserLoginInterceptor並實現了HandlerInterceptor接口(裏面的三個方法均爲jdk8中的新特性----默認方法),重寫了preHandle()方法。如果用戶已登錄,則放行;否則,重定向到登錄頁面login.html
preHandle(): 在請求時,進入到Controller中的方法之前進行調用preHandle()方法。如果返回true,繼續訪問Controller中的方法;否則,終止請求。
具體的內容可以查看: 處理器攔截器(HandlerInterceptor)詳解
註冊攔截器:
僅僅寫了個登錄攔截器並通過IOC注入到Spring容器中還不行,必須通過一個配置類進行配置此攔截器,才能生效。此配置類需要實現一些規範接口。它可以實現WebMvcConfigurer接口,或者繼承WebMvcConfigurationSupport類。WebMvcConfigurerAdapter類已經被廢棄了
配置類:
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private UserLoginInterceptor userLoginInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 1.註冊攔截器
InterceptorRegistration interceptor = registry.addInterceptor(userLoginInterceptor);
// 2. 配置攔截規則
interceptor
.addPathPatterns("/cart/list");
}
}
新建一個配置類InterceptorConfig繼承了WebMvcConfigurationSupport 類,並重寫了addInterceptors()方法。在此方法中,配置了攔截規則:"/cart/list"。它只會攔截"/cart/list"的路徑,即:
當請求路徑爲“localhost:8080/cart/list”時,會判斷用戶是否登錄。
靜態資源不能訪問,原因何在?
進行了如上配置後,你啓動項目,再次進行訪問靜態資源,其結果是不能訪問得 到的。
在攔截器中,我並未設置攔截靜態資源的規則。爲什麼我添加了攔截器後,就不能訪問靜態資源呢?但你若不註冊此攔截器,發現又是能訪問靜態資源的。初步判斷就是攔截器搗地蛋。
查看源碼,真相大白?
查看源碼,究其原因。因爲spring boot 2.x依賴的spring 5.x版本,相對於spring boot 1.5.x依賴的spring 4.3.x版本而言,針對資源的攔截器初始化時有區別,具體源碼在WebMvcConfigurationSupport中。
源碼spring 4.3.x:
@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的源碼:
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, contentNegotiationManager, urlPathHelper);
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping == null) {
return null;
}
handlerMapping.setPathMatcher(pathMatcher);
handlerMapping.setUrlPathHelper(urlPathHelper);
// 此處是將所有的HandlerInterceptor都添加了(包含自定義的HandlerInterceptor)
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
protected final Object[] getInterceptors(
FormattingConversionService mvcConversionService,
ResourceUrlProvider mvcResourceUrlProvider) {
if (this.interceptors == null) {
// 使用構造器初始化了InterceptorRegistry對象
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
- 在配置類中InterceptorConfig調用了addInterceptors()方法,來註冊攔截器。看看下面一個代碼語句
// 1.註冊自定義的攔截器
InterceptorRegistration interceptor = registry.addInterceptor(userLoginInterceptor);
調用了InterceptorRegistry 類中的addInterceptor()方法。
- 查看addInterceptor()方法
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
// 初始化了InterceptorRegistration對象,並傳入了自定義的HandlerInterceptor 對象
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
this.registrations.add(registration);
return registration;
}
- 查看InterceptorRegistration構造器
public InterceptorRegistration(HandlerInterceptor interceptor) {
Assert.notNull(interceptor, "Interceptor is required");
this.interceptor = interceptor;
}
把自定義的HandlerInterceptor 對象賦值給了InterceptorRegistration類中的屬性interceptor
- InterceptorRegistration類是InterceptorRegistry類的一個屬性
public class InterceptorRegistry {
private final List<InterceptorRegistration> registrations = new ArrayList<>();
...
}
- 在getInterceptors()方法中調用了registry.getInterceptors()
this.interceptors = registry.getInterceptors();
- 查看InterceptorRegistry類中的getInterceptors()方法
protected List<Object> getInterceptors() {
return this.registrations.stream()
.sorted(INTERCEPTOR_ORDER_COMPARATOR)
.map(InterceptorRegistration::getInterceptor)
.collect(Collectors.toList());
}
就是對InterceptorRegistry類中屬性registrations(InterceptorRegistration類型的List集合)進行遍歷。而這個集合存儲了SpringBoot中默認的攔截器和用戶的自定義攔截器
- 所以,getInterceptors()是返回了所有的HandlerInterceptor(包含自定義的HandlerInterceptor)
大多博客都是這樣寫的-----從源碼分析出:使用spring 5.x時,靜態資源也會執行自定義的攔截器。但令我不解的是,自定義攔截器並沒有配置攔截靜態資源的規則,那麼靜態資源爲何不顯示呢?其實,我也不清楚。我希望以後能有機會補上這個坑。。。
解決方案
雖然,我並沒有弄清楚上面那個問題的本質,但解決方案確實找到了。
修改配置類:
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Autowired
private UserLoginInterceptor userLoginInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// 1.註冊攔截器
InterceptorRegistration interceptor = registry.addInterceptor(userLoginInterceptor);
// 2. 配置攔截、非攔截規則
interceptor
.addPathPatterns("/cart/list")
.excludePathPatterns("/static/**");
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
- addResourceHandlers()方法:把resources/static文件夾下的靜態資源映射爲"/static/"。每次請求時,需要添加“/static”
- addInterceptors()方法:配置了不攔截的規則,這樣,訪問靜態資源時,就能訪問了
舉例:在頁面顯示resources/static/img/a.jpg圖片。
<img src="/static/img/a.jpg" />
【參考資料】: