在 Web 应用开发中,常常需要对用户的请求进行一些预处理或者后处理操作,比如权限验证、日志记录、性能监控等。SpringMVC 提供了一种强大且灵活的机制来实现这些功能,那就是拦截器(Interceptor)。本文将深入探讨 SpringMVC 拦截器的相关知识,包括其基本概念、与过滤器的区别、具体方法、执行流程以及实际应用案例。
一、什么是拦截器
SpringMVC 的拦截器机制类似于 Servlet 中的 Filter 过滤器,它的主要作用是拦截用户的请求,并在请求到达目标控制器(Controller)之前或者之后进行相应的处理。例如,我们可以利用拦截器来进行用户权限验证,判断用户是否已经登录;也可以用来记录请求的相关信息,进行日志处理等。SpringMVC 拦截器采用了可插拔式的设计,这意味着如果我们需要某个功能的拦截器,只需要在配置文件中进行相应配置即可;而如果不需要,取消配置就行,非常方便灵活。
二、拦截器和过滤器的区别
- 所属框架不同:过滤器(Filter)依赖于 Servlet 容器,是 Servlet 规范的一部分;而拦截器(Interceptor)则是 SpringMVC 框架特有的技术。
- 作用范围不同:过滤器可以对所有进入 Servlet 容器的请求起作用,包括对静态资源(如 HTML、CSS、JavaScript 文件等)的请求;而拦截器只对访问 Controller 层的请求起作用,对于静态资源的请求等它不会进行拦截处理。
- 执行顺序不同:过滤器的执行时机是在请求进入 Tomcat 容器之后,但在请求进入 Servlet 之前;而拦截器是在 Servlet 之后,Controller 控制器方法调用之前以及之后执行,即拦截器是在 Servlet 和 Controller 之间执行。
三、拦截器方法详解
在 SpringMVC 中,想要自定义拦截器,需要实现HandlerInterceptor
接口。该接口定义了三个重要的方法,分别是preHandle
、postHandle
和afterCompletion
,下面我们来详细了解一下这三个方法:
(一)preHandle 方法
- 执行时机:在执行目标控制器方法之前执行。
- 返回值及作用 :返回值为
Boolean
类型。如果返回false
,表示对当前请求进行拦截,后续的目标控制器方法以及其他拦截器的相关方法(如果有多个拦截器)都不会再执行;如果返回true
,则表示放行该请求,程序会继续向下执行(如果后面没有其他 Interceptor,就会执行 Controller 方法)。这个方法非常适合用于对请求进行判断,决定程序是否继续执行,也可以进行一些初始化操作或者对请求进行预处理,比如检查用户的登录状态、验证请求参数的合法性等 。
(二)postHandle 方法
- 执行时机 :在目标控制器方法调用之后,且在返回
ModelAndView
之前执行。 - 作用 :由于该方法会在
DispatcherServlet
进行返回视图渲染之前被调用,所以可以利用此方法对请求域中的模型(Model)和视图(View)做进一步的修改。例如,我们可以在这个方法中根据业务需求动态地添加一些数据到模型中,或者对视图的相关配置进行调整等 。
(三)afterCompletion 方法
- 执行时机 :在整个请求处理完成,即
DispatcherServlet
处理完请求,且视图渲染完毕之后执行。 - 作用:该方法适合进行一些资源清理、记录日志信息等处理操作。比如关闭数据库连接、释放其他占用的资源,或者记录请求处理完成后的相关状态信息等 。
四、单个拦截器的执行流程
当一个请求进入 SpringMVC 应用,并且配置了单个拦截器时,其执行流程如下:
- 程序首先会执行拦截器类中的
preHandle()
方法。如果该方法返回值为true
,则程序继续向下执行目标控制器(即业务处理器,Controller 类)中的方法;如果返回值为false
,则请求被拦截,后续的控制器方法以及该拦截器的其他方法都不会再执行。 - 当目标控制器处理完请求后,会执行拦截器的
postHandle()
方法。 - 最后,在
DispatcherServlet
处理完请求,向客户端返回响应后,才会执行拦截器的afterCompletion()
方法。
五、使用拦截器实现用户登录权限验证
(一)Controller 层设计
我们定义一个LoginController
来处理与用户登录相关的请求,代码如下:
@Controller
public class LoginController {
/**
* 跳转登录页
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String loginPage(){
System.out.println("跳转到login.html页面当中");
return "login";
}
/**
* 用户登录,成功到主页,失败回到登录页
* @param user
* @param model
* @param session
* @return
*/
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(User user, Model model, HttpSession session){
if(user.getUsername() !=null && user.getUsername().equals("admin")
&& user.getPassword() !=null && user.getPassword().equals("123456")){
System.out.println("用户登录功能实现");
//将用户添加到session保存
session.setAttribute("user",user);
return "/suc";
}
model.addAttribute("msg","账户或密码错误,请重新登录");
return "login";
}
/**
* 跳转到主页
* @return
*/
@RequestMapping("/index")
public String indexPage(){
System.out.println("跳转到主页");
return "suc";
}
/**
* 用户退出登录
* @param session
* @return
*/
@RequestMapping("/logout")
public String logout(HttpSession session){
session.invalidate();//清除session
System.out.println("用户退出登录");
return "login";
}
}
在上述代码中,LoginController
包含了处理用户登录、跳转登录页、跳转到主页以及用户退出登录等功能的方法。通过@RequestMapping
注解来映射不同的请求路径,并根据业务逻辑进行相应的处理,比如验证用户输入的账号密码是否正确,以及对用户的登录状态进行管理等 。
(二)登录页面
登录页面(login.html
)的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1> <font color="red"> <b th:text="${msg}"></b></font></h1>
<form action="/SSMDemo/login" method="post">
账户:<input type="text" name="username"/>
密码:<input type="password" name="password"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
该页面提供了一个简单的表单,用于用户输入账号和密码进行登录。如果登录失败,会从后端获取错误信息并显示在页面上。
(三)主页
主页(suc.html
)的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello <b th:text="${msg}"></b></h1>
<a href="/SSMDemo/logout" >入门程序</a>
</body>
</html>
当用户成功登录后,会跳转到该主页。主页上显示欢迎信息,并提供了一个用户退出登录的链接。
(四)拦截器配置
我们定义一个LoginInterceptor
来实现登录权限验证的拦截器功能,代码如下:
/**
* 登录拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求url
String url = request.getRequestURI();
//非登录请求进行拦截
if (!url.contains("login")){
//非登录请求获取session
if(request.getSession().getAttribute("user") != null){
return true;//说明已经登录,放行
}else { //没有登录,跳转到登录页面
request.setAttribute("msg","您还没登录。请先登录。。。");
request.getRequestDispatcher("/html/login.html").forward(request,response);
}
}else {
return true; //登录请求,放行
}
return true;
}
//省略了postHandle()和afterCompletion()方法
}
在LoginInterceptor
的preHandle
方法中,首先获取请求的 URL,然后判断是否是登录请求。如果不是登录请求,则检查用户的会话(Session)中是否存在用户信息。如果存在,说明用户已经登录,放行请求;如果不存在,说明用户未登录,则将提示信息设置到请求域中,并将请求转发到登录页面。
同时,还需要在springMV.xml
文件中配置拦截器,配置代码如下:
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/> <!--/**表示所有url-->
<bean class="com.qcby.Interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
上述配置表示对所有的 URL 请求都应用LoginInterceptor
拦截器,这样就可以对整个应用的请求进行登录权限验证了。
六、多个拦截器的执行流程
当 SpringMVC 应用中配置了多个拦截器时,它们的执行流程有一定的规则。具体来说,多个拦截器的preHandle()
方法会按照配置文件中拦截器的配置顺序依次执行;而它们的postHandle()
方法和afterCompletion()
方法则会按照配置顺序的反序执行。
假设有两个拦截器Interceptor1
和Interceptor2
,并且在配置文件中,Interceptor1
拦截器配置在前。那么执行流程如下:
- 首先执行
Interceptor1
的preHandle()
方法,如果返回true
,接着执行Interceptor2
的preHandle()
方法。如果其中任何一个拦截器的preHandle()
方法返回false
,则后续的拦截器preHandle()
方法以及目标控制器方法都不会执行。 - 当目标控制器方法执行完毕后,先执行
Interceptor2
的postHandle()
方法,然后执行Interceptor1
的postHandle()
方法。 - 最后,在整个请求处理完成后,先执行
Interceptor2
的afterCompletion()
方法,再执行Interceptor1
的afterCompletion()
方法。
这种执行顺序的设计可以让我们根据业务需求灵活地安排多个拦截器的执行逻辑,比如在前面的拦截器中进行一些通用的预处理操作,在后面的拦截器中进行一些更具体的业务相关的检查或处理等 。
七、总结
SpringMVC 拦截器是一种非常实用的技术,它为我们在 Web 应用开发中处理请求提供了很大的灵活性和便利性。通过合理地使用拦截器,我们可以轻松实现诸如用户权限验证、日志记录、性能监控等功能,并且可以根据业务需求灵活地配置单个或多个拦截器。掌握拦截器的原理和使用方法,对于提升 SpringMVC 应用的开发效率和质量有着重要的意义。希望本文的内容能够帮助大家更好地理解和应用 SpringMVC 拦截器。