前言
接到一个需求,将已开发完成的系统接入权限系统。其中需要在多处请求返回的ModelAndView中添加权限代码集合,方便显示层做菜单按钮的显示控制。所以当时想法是新建一个拦截器对这几个请求做统一处理(放入权限代码集合),类似之前项目的错误页面拦截器。
拦截器简介
Spring 框架实现拦截器功能主要有两种方法,第一种是实现HandlerInterceptor
接口,第二种是实现WebRequestInterceptor
接口。
HandlerInterceptor接口
通过实现HandlerInterceptor接口,重写方法preHandle()
、postHandle()
和afterCompletion()
拦截请求。Spring 框架中还提供了另外一个接口和一个抽象类,实现了对HandlerInterceptor接口的功能扩展,分别为:AsyncHandlerInterceptor和HandlerInterceptorAdapter。
对于AsyncHandlerInterceptor接口,其在继承HandlerInterceptor接口的同时,又声明了一个新的方法afterConcurrentHandlingStarted()
;
而HandlerInterceptorAdapter抽象类,在其继承AsyncHandlerInterceptor接口的同时,又复写了preHandle方法。
preHandle(HttpServletRequest request, HttpServletResponse response, Object handle)
方法,该方法在请求处理之前进行调用。SpringMVC 中的 Interceptor 是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个 Interceptor 。每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求做一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值 Boolean 类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时,就会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候,就会是调用当前请求的 Controller 中的方法。postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)
方法,通过 preHandle 方法的解释咱们知道这个方法包括后面要说到的 afterCompletion 方法都只能在当前所属的 Interceptor 的 preHandle 方法的返回值为 true 的时候,才能被调用。postHandle 方法在当前请求进行处理之后,也就是在 Controller 中的方法调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以咱们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。postHandle 方法被调用的方向跟 preHandle 是相反的,也就是说,先声明的 Interceptor 的 postHandle 方法反而会后执行。这和 Struts2 里面的 Interceptor 的执行过程有点类型,Struts2 里面的 Interceptor 的执行过程也是链式的,只是在 Struts2 里面需要手动调用 ActionInvocation 的 invoke 方法来触发对下一个 Interceptor 或者是 action 的调用,然后每一个 Interceptor 中在 invoke 方法调用之前的内容都是按照声明顺序执行的,而 invoke 方法之后的内容就是反向的。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
方法,也是需要当前对应的 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。因此,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行,这个方法的主要作用是用于进行资源清理的工作。
WebRequestInterceptor接口
在WebRequestInterceptor接口中也定义了 3 个方法,方法名同HandlerInterceptor接口相同,3 个方法都传递了同一个参数 WebRequest。WebRequest 是 Spring 中定义的一个接口,它里面的方法定义跟 HttpServletRequest 类似,在WebRequestInterceptor中对 WebRequest 进行的所有操作都将同步到 HttpServletRequest 中,然后在当前请求中依次传递。
Spring框架中还提供了WebRequestInterceptorAdapter,其实现了AsyncHandlerInterceptor接口,并在内部调用了WebRequestInterceptor接口。
preHandle(WebRequest request)
方法,该方法在请求处理之前进行调用,也就是说,其会在 Controller 中的方法调用之前被调用。这个方法跟 HandlerInterceptor 中的 preHandle 不同,主要区别在于该方法的返回值是void 类型的,也就是没有返回值,因此我们主要用它来进行资源的准备工作,比如我们在使用 Hibernate 的时候,可以在这个方法中准备一个 Hibernate 的Session 对象,然后利用 WebRequest 的 setAttribute(name, value, scope) 把它放到 WebRequest 的属性中。在这里,进一步说说 setAttribute 方法的第三个参数 scope ,该参数是一个Integer 类型的。在 WebRequest 的父层接口 RequestAttributes 中对它定义了三个常量,分别为:- SCOPE_REQUEST ,它的值是 0,表示只有在 request 中可以访问。
- SCOPE_SESSION,它的值是1,如果环境允许的话,它表示的是一个局部的隔离的 session,否则就代表普通的 session,并且在该 session 范围内可以访问。
- SCOPE_GLOBAL_SESSION,它的值是 2,如果环境允许的话,它表示的是一个全局共享的 session,否则就代表普通的 session,并且在该 session 范围内可以访问。
postHandle(WebRequest request, ModelMap model)
方法,该方法在请求处理之后,也就是在 Controller 中的方法调用之后被调用,但是会在视图返回被渲染之前被调用,所以可以在这个方法里面通过改变数据模型 ModelMap 来改变数据的展示。该方法有两个参数,WebRequest 对象是用于传递整个请求数据的,比如在 preHandle 中准备的数据都可以通过 WebRequest 来传递和访问;ModelMap 就是 Controller 处理之后返回的 Model 对象,咱们可以通过改变它的属性来改变返回的 Model 模型。afterCompletion(WebRequest request, Exception ex)
方法,该方法会在整个请求处理完成,也就是在视图返回并被渲染之后执行。因此可以在该方法中进行资源的释放操作。而 WebRequest 参数就可以把咱们在 preHandle 中准备的资源传递到这里进行释放。Exception 参数表示的是当前请求的异常对象,如果在 Controller 中抛出的异常已经被 Spring 的异常处理器给处理了的话,那么这个异常对象就是是 null.
来源:作者:维C果糖 | 文章:述 Spring MVC 框架中拦截器 Interceptor 的使用方法
拦截器CODE
-
实现HandlerInterceptor 接口
import cn.com.do1.component.core.shirocas.model.UserVO; import cn.com.do1.component.util.HttpPermissionUtil; import org.apache.shiro.SecurityUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; /** * 点击菜单拦截器 * @author wm * @date 2018.10.29 */ @Component public class ClickMenuInterceptor implements HandlerInterceptor { @Value("${qxws.identifier}") private String systemId; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { UserVO user = (UserVO) SecurityUtils.getSubject().getSession().getAttribute("user"); List<String> buttonList = HttpPermissionUtil.getButtonPermission(user.getUserId(), systemId); modelAndView.addObject("buttonList",buttonList); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
`
-
实现WebRequestInterceptor接口
import org.springframework.ui.ModelMap; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequestInterceptor; /** * @author wm * @Description ClinkMenuInterceptor2 * @date 2018/10/30 15:34 * @Version 1.0 */ public class ClinkMenuInterceptor2 implements WebRequestInterceptor { @Override public void preHandle(WebRequest request) throws Exception { } @Override public void postHandle(WebRequest request, ModelMap model) throws Exception { } @Override public void afterCompletion(WebRequest request, Exception ex) throws Exception { } }
`
-
继承HandlerInterceptorAdapter类
import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /** * 错误页面拦截器 * 替代EmbeddedServletContainerCustomizer在war中不起作用的方法 * @author wm */ @Component public class ErrorPageInterceptor extends HandlerInterceptorAdapter { private List<Integer> errorCodeList = Arrays.asList(404,500); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (errorCodeList.contains(response.getStatus())) { response.sendRedirect(request.getContextPath()+"/error/" + response.getStatus()); return false; } return super.preHandle(request, response, handler); } }
`
配置
-
SpringMVC
<!--权限接入:拦截器--> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/identitySource/goIdentitySourceList"/> <mvc:mapping path="/userManager/goUserManagerListPage"/> <mvc:mapping path="/schedulemgr/goScheduleListPage"/> <bean class="cn.com.do1.component.security.interceptor.ClickMenuInterceptor"/> </mvc:interceptor> </mvc:interceptors>
-
SpringBoot
@Bean public ClickMenuInterceptor getClickMenuInterceptor() { return new ClickMenuInterceptor(); } /** * 新增security拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getClickMenuInterceptor()). addPathPatterns("/identitySource/goIdentitySourceList", "/userManager/goUserManagerListPage", "/schedulemgr/goScheduleListPage"); super.addInterceptors(registry); }
@Autowired private ErrorPageInterceptor errorPageInterceptor;//错误拦截器(拦截404,500) /** * 新增拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(errorPageInterceptor);//默认拦截所有 super.addInterceptors(registry); }