SpringMVC 拦截器(Interceptor)

一.拦截器

假设有这么一个场景,一个系统需要用户登录才能进入,在检验完用户的信息后对页面进行了跳转。但是如果我们直接输入跳转的url,可以绕过用户信息校验(用户登录),直接进入系统。

因此我们引入了使用session检验,我们在用户登录的时候创建session,并将这个用户的信息存入session中(setAttribute),这样我们跳转其他的url时就可以对session进行检验。

java 复制代码
HttpSession httpSession=httpServletRequest.getSession(false);
if(httpSession==null){
    System.out.println("没有创建会话");
    return new User();
}
User user=(User) httpSession.getAttribute("user");
if(user==null){
    System.out.println("没有该用户");
    return new User();
}

但是这种方法每写一个接口就要重复一遍,有没有什么办法能不写这些重复的内容。这就是拦截器的内容了。

拦截器属于SpringMVC框架,是 Spring 生态的核心组件之一。拦截器主要用来拦截用户请求,在指定方法前后执行业务代码。总的来说,拦截器是 Spring 送给 Web 开发者的 "定制化关卡",只在 Spring 的世界里生效。

使用拦截器的方法:

1)定义拦截器;2)注册配置拦截器。

二.实现流程

1.定义拦截器

自定义一个拦截器,实现 HandlerInterceptor接口,并重写其方法。

1)**preHandle()**方法:在目标方法执行前执行,返回true继续执行后续操作,返回false拦截后续操作;

2)**postHandle()**方法:在目标方法执行后执行,无返回值

3)**afterCompletion()**方法:视图渲染完毕后执行。

我们可以将我们的业务逻辑写在这些方法中。比如上面的例子,我们的目的就是为了拦截用户在没有登录的时候就访问系统内部的url,所以我们可以写一个拦截器:

java 复制代码
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpSession session=request.getSession(false);
    if(!checkUser(session)){
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(401);
        String msg="用户未登录哦";
        response.getOutputStream().write(msg.getBytes("UTF-8"));
        return false;
    }
    return true;
}
private boolean checkUser(HttpSession session){
    if(session==null){
        log.warn("用户未登录,session==null");
        return false;
    }
    UserInfo userInfo=(UserInfo)session.getAttribute("userInfo");
    if(userInfo==null){
        log.warn("用户未登录,userInfo==null");
        return false;
    }
    log.info("用户已登录");
    return true;
}

最后不要忘记将我们定义实现 HandlerInterceptor接口的类注入到Spring容器中。

这样我们的拦截器就定义完了。

2.注册配置拦截器

我们要实现 WebMvcConfigurer 接口,并重写其中 addInterceptors方法。

举例实现:

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**");
    }
}

我们通过addPathPatterns 来指定要拦截的路径,也可以使用excludePathPatterns 来指定不要拦截的路径。像上面的代码,我们一次性拦截了所有请求,用户登录请求也被拦截了,这是不行的。因此我们可以改一下上面的代码,使用excludePathPatterns来指定用户登录的请求不要拦截。

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/uer/login");
    }
}

下面是一些常见的拦截配置:

|------------|---------------|------------------------------------------------|
| 拦截路径 | 含义 | 举例 |
| /* | 拦截一级路径 | 能拦截 /user,/login,不能拦截 /user/login |
| /** | 拦截任意路径 | 拦截所有 |
| /user/* | 拦截/user的下一级路径 | 能拦截 /user/getList,不能拦截 /user/getList/1 和 /user |
| /user/** | 拦截/user下的所有路径 | 拦截/user下的所有路径,但是不能拦截 /book/getList |

此外拦截器不仅可以拦截项目中的URL,还可以拦截静态资源(html,图片等)。

三.拦截器与前端的交互

最近做项目的时候突然意识到一个问题,我们确确实实拦截了一些请求,返回false了。但是前端没有处理,导致我们访问一些被拦截的请求,但是不知道为什么被拦截了。

简单来说,我想让拦截器返回false,并且浏览器给个 alert ,告诉用户为什么被拦截了。

因为拦截器返回 false 意味着请求被中断,后端不会继续执行控制器,所以传统的通过控制器返回数据的方式不可行。所有我们的核心方案是状态码 + 自定义头 + 响应体。

具体操作:

java 复制代码
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpSession session=request.getSession(false);
    if(!checkUser(session)){
        // 1. 设置AJAX友好的响应格式
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401状态码
        // 2. 返回结构化的拦截信息(含提示语和跳转指令)
        String json = "{\"code\":401, \"msg\":\"用户未登录哦,请重新登录\",\"redirect\":\"login.html\"}";
        response.getOutputStream().write(json.getBytes("UTF-8"));
        return false;
    }
    return true;
}

后端设置好状态码等信息,前端接收状态码,并执行操作:

javascript 复制代码
$.ajax({
    type: "GET",
    url: "/book/getListByPage" + location.search,
    xhrFields: { withCredentials: true }, // 携带Cookie(登录状态)
    statusCode: { // 关键:捕获特定状态码
        401: function(xhr) { // 拦截器返回的401状态
            const interceptData = JSON.parse(xhr.responseText);
            alert("用户没有登录"); 
            if (interceptData.redirect) {
                location.href = interceptData.redirect;
            }
        }
    },
    //success执行的是成功后的逻辑
    success: function (result) {}
});
相关推荐
异常君4 分钟前
Redis 中的概率过滤器:布隆过滤器与布谷鸟过滤器实战对比
java·redis·后端
胡子发芽5 分钟前
请解释Java中的逃逸分析(Escape Analysis)及其对性能的影响,并说明如何通过JVM参数来控制逃逸分析的行为
java
Stimd7 分钟前
【重写SpringFramework】声明式事务上:构建事务切面(chapter 4-5)
java·后端·spring
码熔burning7 分钟前
【MQ篇】RabbitMQ之消息持久化!
java·分布式·rabbitmq·mq
南客先生10 分钟前
深入解析:RocketMQ、RabbitMQ和Kafka的区别与使用场景
java·kafka·消息队列·rabbitmq·rocketmq
caihuayuan412 分钟前
【docker&redis】用docker容器运行单机redis
java·大数据·sql·spring·课程设计
我是苏苏15 分钟前
消息中间件RabbitMQ02:账号的注册、点对点推送信息
开发语言·后端·ruby
写bug写bug32 分钟前
Java并发编程:优雅的关闭钩子(Shutdown Hook)
java·后端
工藤新一¹33 分钟前
C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 14)
开发语言·c++·游戏引擎·游戏开发·sdl·实践项目
钢铁男儿40 分钟前
C#核心技术解析:静态类型、dynamic与可空类型
开发语言·c#