一、使用方法
对于在Spring Boot2.0中使用拦截器来说,其使用方法与Spring Boot1.0并无很大区别 。
我的需求是实现登录拦截,通过在session中判断有没有登录用户名来实现拦截
首先就是建立一个实现了HandlerInterceptor的拦截器类。
如下:
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object userName = request.getSession().getAttribute("loginUserName");
if(StringUtils.isEmpty(userName)){
request.setAttribute("msg","您没有操作权限");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
return true;
}
}
这里要提一句的是,Spring Boot2.0是基于Java8的,其中接口可以有默认实现,也就是说你实现一个接口并不用实现它的所有方法。如上,我只实现了preHandle方法。
之后的任务就是将自定义的拦截器添加到InterceptorRegistry中去,这个工作在Spring Boot1.0的时候可以通过继承WebMvcConfigurerAdapter完成,但在Spring Boot2.0中这个适配类是被弃用了的,所以我们可以直接实现WebMvcConfigurer来完成拦截器的添加。
如下:
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","user/login","/index.html")
.excludePathPatterns("/public/**","/resources/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//第一个方法设置访问路径前缀,第二个方法设置资源路径
registry.addResourceHandler("/resources/**","/public/**")
.addResourceLocations("classpath:/resources/","classpath:/public/");
}
}
在实现WebMvcConfigurer之后可以选择你要重写的方法,这里重写addInterceptors这个方法来添加自定义的拦截器。
-
addInterceptor用于添加你自定义的拦截器实例
-
addPathPatterns用于添加要拦截的url,可以写多个。
-
excludePathPatterns用于添加不需要拦截的url,可以写多个。
扫描二维码关注公众号,回复: 3397382 查看本文章
至此,我们的登录拦截器就完成了。
二、疑难解惑
1.excludePathPatterns失效的问题
这个现象就是你在excludePathPatterns方法中添加了要忽略的路径,但当你访问此路径的时候拦截器依然进行了拦截。
这是因为你要忽略的路径在项目中并不存在,springboot会将路径编程/error,从而无法进行排除
2. 静态资源被拦截的问题
在Spring Boot1.0中,我们自定义的拦截器并不会对静态资源做出拦截,但是在Spring Boot2.0中,我们自定义的拦截器对静态资源同样做出了拦截。
具体原因私以为如下:
在SpringBoot引入Web模块之后,就会进行WebMvc的自动化配置,即:WebMvcAutoConfiguration,
在WebMvcAutoConfiguration中还有个内部静态配置类,即:EnableWebMvcConfiguration,
EnableWebMvcConfiguration是继承了DelegatingWebMvcConfiguration类,
而DelegatingWebMvcConfiguration又继承了WebMvcConfigurationSupport类:
所以当开启WebMvcAutoConfiguration的自动配置后,WebMvcConfigurationSupport中的bean也会自动加载。
其中WebMvcConfigurationSupport的resourceHandlerMapping
方法就是处理静态资源映射的:
对Spring Boot 1.0依赖的spring-webmvc-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() {
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());
// 这里添加一个静态资源拦截器
handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
这个方法中找到这行代码:
handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
由此处可知,在spring-webmvc-4.3.x版本中,静态资源处理器有固定的拦截器。
而在spring-webmvc-5.0.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());
// 这里通过getInterceptors()找拦截器
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
给静态资源处理器添加拦截器的代码是:
handlerMapping.setInterceptors(getInterceptors());
getInterceptors()的代码是:
/**
* Provide access to the shared handler interceptors used to configure
* {@link HandlerMapping} instances with.
* <p>This method cannot be overridden; use {@link #addInterceptors} instead.
*/
protected final Object[] getInterceptors() {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
其中:
InterceptorRegistry registry = new InterceptorRegistry();
addInterceptors(registry);
这里可以获取到配置类当中自定义的HandlerInterceptor,之后将自定义的拦截器添加到拦截器链中,而这个方法返回的是所有的拦截器,其中包括我们自定义的。所以每个静态资源的请求都会被自定义Interceptor拦截。
解决办法
因为自定义的拦截器拦截了所有的路径,所以首先我们需要重写addResourceHandlers()方法,指定静态资源的访问路径前缀以及静态资源所处路径:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//第一个方法设置访问路径前缀,第二个方法设置资源路径
registry.addResourceHandler("/resources/**","/public/**")
.addResourceLocations("classpath:/resources/","classpath:/public/");
}
然后在添加自定义拦截器时忽略静态资源的路径前缀:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","user/login","/index.html","/error.html")
.excludePathPatterns("/public/**","/resources/**");
}
最后,在访问静态资源的时候,加上资源所处的完整路径,例如
Spring boot1.0可以这样访问静态资源:
localhost:8080/11.png
那么Spring Boot2.0加上自定义拦截器就得这样了:
localhost:8080/public/11.png
、localhost:8080/resources/11.png