对Spring MVC拦截器的理解
在平常练手的项目中,对于用户认证以及用户权限管理往往都是通过SpringMVC 拦截器以及其他手段进行处理,然而当项目规模变大,系统安全性要求增加的时候,基于SpringMVC 拦截器等实现的用户认证功能已经不能满足系统需求。常见的手段为利用Spring Security、Apache Shiro 等常见安全框架进行替换。本文首先介绍一下SpringMVC 拦截器的相关知识。
1.Spring MVC
Spring MVC是SSM中的一件套,它是由Spring提供的一个Web框架,借助于注解,使得控制器(Controller)的开发与测试更加简单。Spring MVC通常由以下几个部分构成:DispatcherServlet(核心)、HandlerMapping、controller、ViewResolver等。
1.1 运行原理
- 客户端(浏览器)将请求(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)直接发送至DispatcherServlet
- DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的Handler(Controller)。
- DispatcherServlet将请求提交至对应的Handler(其实就是Controller),开始由HandlerAdapter 适配器处理
- Controller调用业务逻辑对用户的请求进行处理
- 处理完成后返回一个ModelAndView对象(包含了数据模型以及相应的视图的信息)给DispatcherServlet。
- ModelAndView中的视图是逻辑视图,DispatcherServlet借助ViewResolver(视图解析器)完成真实视图对象的解析
- 当得到真实的视图对象后,DispatcherServlet利用真实视图对ModelAndView中的Mode数据对象进行渲染。
- 把View返回给浏览器。
2.Spring MVC拦截器
2.1 概述
Spring MVC中的Interceptor拦截器机制主要用于拦截用户的请求并做出相应的处理,如下图所示,可以发现Interceptor位于DispatcherServlet以及Controller之间,所以能够用于拦截对Controller层的相关请求。Interceptor拦截器常用于用户权限认证以及判断用户是否登录等场景。
2.2 拦截器的两种实现方式
2.2.1 通过实现HandlerInterceptor接口
(1)拦截器的实现
在SpringMVC中,拦截器的实现一般都是通过实现HandlerInterceptor
接口,并重写该接口中的3个方法:preHandle()
、postHandle()
、afterCompletion()
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
preHandle
preHandle拦截器作用于用户请求到达Controller之前,如果需要对用户的请求做预处理,可以选择在该方法中完成。3种方法中唯一带有返回值的方法:
- 如果返回值为true,则继续执行之后的拦截器或者Controller
- 如果返回值为false,则不再执行后面的拦截器和Controller
postHandle
执行完Controller之后,利用model渲染真实视图之前,作用场景为需要对响应的相关数据进处理。
afterCompletion
调用完Controller接口,渲染View页面后调用,同时如果prehandle方法的返回值为true,则也会执行该方法。
(2)拦截器的配置
实现拦截器之后,需要对拦截器进行配置,默认情况下拦截器将会拦截所有请求,包括静态资源等等,而静态资源(图片、css、js等)的请求一般是不需要拦截的。同时也可以自定义需要拦截的请求。
2.2.2 使用自定义注解实现拦截器
(1)自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 自定义注解需要用元注解标记
// 四种常见的元注解@Target(自定义注解的作用类型,例如类、方法、属性等)
// @Retention(自定义注解有效时间,例如编译时,运行时)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
(2)在Controller中对相应方法进行自定义注解的标注
@LoginRequired
@RequestMapping(path = "/setting",method = RequestMethod.GET)
public String getSettingPage(){
return "/site/setting";
}
(3)利用反射获取注解
拦截器会拦截所有请求,但是我们通过判断可以实现只对带有该注解的方法进行处理。
import com.nowcoder.community.annotation.LoginRequired;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 因为我们的目标是只拦截方法,而拦截器有可能会拦截其他资源,所以必须先判断拦截器拦截的目标handler是否为方法
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 利用反射获取注解
LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
if(loginRequired != null && hostHolder.getUser() == null){
// 如果用户未登录,则重定向至登录界面
response.sendRedirect(request.getContextPath() + "/login");
// 拒绝本次请求
return false;
}
}
return true;
}
}
自定义注解实现拦截器的好处在于可以避免对拦截器进行配置。
2.3 源码底层
上文说到,所有请求都会直接先传递至DispatcherServlet,所以先分析一下DispatcherServlet,在该类中,最重要的一个方法就是doDispatch,查看部分核心源码之后,你会发现DispatcherServlet就是依赖该方法进行任务分配。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//返回所有拦截器
mappedHandler = this.getHandler(processedRequest);
if(mappedHandler == null || mappedHandler.getHandler() == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//获取能够处理当前请求所对应的适配器,并用于调用Controller中逻辑代码。
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//调用所有拦截器的preHandle方法
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
//如果拦截器的preHandle方法返回值为false,则结束该方法的执行
return;
}
//执行Controller中的逻辑代码,获取到ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//调用所有拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var19) {
dispatchException = var19;
}
//处理视图渲染
this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception var20) {
//如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
}
}