基于黑马点评的拦截器与过滤器详解

黑马点评系列

基于黑马点评的springboot启动流程梳理


过滤器(Filter)和拦截器(Interceptor)都是基于 AOP(Aspect Oriented Programming,面向切面编程)思想实现的,用来解决项目中某一类问题的两种“工具”,但二者有着明显的差距。


一、拦截器和过滤器是什么?

拦截器Interceptor )是Spring MVC框架中用于在请求处理之前或之后执行某些逻辑的组件。它可以在控制器方法执行前后进行拦截,从而实现如权限验证、日志记录、性能监控等功能。

过滤器Filter )是Java Web开发中用于在请求到达Servlet之前或响应返回客户端之前对请求和响应进行预处理和后处理的组件。它可以在多个层面(如字符编码、安全检查、日志记录等)对HTTP请求和响应进行拦截和修改。

二、拦截器和过滤器的区别

1.作用

拦截器
① 主要增强业务逻辑处理前后的功能:如权限验证、用户信息保存、刷新token有效期等。
② 访问控制器方法的相关信息:可以获取到控制器方法的参数、返回值等信息。

过滤器
① 字符编码设置:确保请求和响应使用一致的字符编码,避免乱码问题。
② 安全检查:验证用户身份、权限等,防止未授权访问。
③ 日志记录:记录请求和响应的信息,便于调试和监控。
④ 性能监控:统计请求的处理时间,监控系统性能。
⑤ 数据压缩:对响应内容进行压缩,减少传输量。
⑥ 跨域资源共享(CORS):处理跨域请求,允许或限制特定域名的访问。
直接操作ServletRequest和ServletResponse对象:可以在请求到达目标资源之前或响应返回客户端之后修改请求或响应的内容。


2.执行时机

过滤器(Filter):
请求到达Servlet之前和响应返回客户端之前。

拦截器(Interceptor):
请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束之前。

请求进入容器 > 进入过滤器 > 进入 Servlet > 进入拦截器 > 执行控制器(Controller)

在这里插入图片描述

3.来源与原理

过滤器是 Servlet 规范中定义的,所以过滤器要依赖 Servlet 容器,它只能用在 Web 项目中;而拦截器是 Spring 中的一个组件,因此拦截器既可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中。

过滤器是基于方法回调实现的,而拦截器是基于动态代理(底层是反射)实现的。


4.实现

过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法,具体实现代码如下:

import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*")
public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器:执行 init 方法。");
    }
    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器:开始执行 doFilter 方法。");
        // 请求放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("过滤器:结束执行 doFilter 方法。");
    }
    @Override
    public void destroy() {
        System.out.println("过滤器:执行 destroy 方法。");
    }
}

void init(FilterConfig filterConfig):容器启动(初始化 Filter)时会被调用,整个程序运行期只会被调用一次。用于实现 Filter 对象的初始化。

void doFilter(ServletRequest request, ServletResponse response,FilterChain chain):具体的过滤功能实现代码,通过此方法对请求进行过滤处理,其中 FilterChain 参数是用来调用下一个过滤器或执行下一个流程。

void destroy():用于 Filter 销毁前完成相关资源的回收工作。


拦截器的实现分为两步,第一步,创建一个普通的拦截器,实现 HandlerInterceptor 接口,并重写接口中的相关方法;第二步,将上一步创建的拦截器加入到 Spring Boot 的配置文件中。

黑马点评中的实现如下:

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle):在请求方法执行前被调用,也就是调用目标方法之前被调用。比如我们在操作数据之前先要验证用户的登录信息,就可以在此方法中实现,如果验证成功则返回 true,继续执行数据操作业务;否则就返回 false,后续操作数据的业务就不会被执行了。

黑马点评中的业务实现:
① 获取Token:从请求头中获取authorization字段的值作为Token。
② 检查Token是否存在:如果Token为空,直接放行。
③ 查询Redis中的用户信息:基于Token从Redis中获取用户信息。
④ 判断用户是否存在:如果用户信息为空,直接放行。
⑤ 转换用户信息:将Redis中的用户信息转换为UserDTO对象。
⑥ 保存用户信息到ThreadLocal:将用户信息保存到UserHolder的ThreadLocal中。
⑦ 刷新Token有效期:更新Redis中Token的有效期。
⑧ 放行请求:返回true,继续执行后续的请求处理逻辑。

void postHandle(HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView):调用请求方法之后执行,但它会在 DispatcherServlet 进行渲染视图之前被执行。

void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex):会在整个请求结束之后再执行,也就是在 DispatcherServlet 渲染了对应的视图之后再执行。

黑马点评中的作用:在请求完成后移除当前线程中保存的用户信息。

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
        // token刷新的拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

黑马点评中配置了两个拦截器,一个是登录验证 LoginInterceptor 拦截器,它排除了一些不需要登陆的路径后,其他的路径都会判断当前是否登陆,如果没有登陆会返回401。

package com.hmdp.utils;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser() == null) {
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,则放行
        return true;
    }
}

还有一个是上文中提到的 RefreshTokenInterceptor 拦截器。这两个拦截器用 .order() 来定义拦截器的执行顺序,数字越小则 preHandle 执行越早,postHandle 执行越晚。
过滤器中也可以用 .order() 来定义过滤器的执行顺序,数字越小则 doFilter 执行越早。

总结

过滤器和拦截器都是基于 AOP 思想实现的,用来处理某个统一的功能的,但二者又有 5 点不同:出身不同、触发时机不同、实现不同、支持的项目类型不同以及使用的场景不同。过滤器通常是用来进行全局过滤的,而拦截器是用来实现某项业务拦截的。

### 黑马点评拦截器链的实现用法 在黑马点评项目中,拦截器链是一种基于 Spring MVC 的功能模块设计模式。它通过 `HandlerInterceptor` 接口来定义具体的拦截逻辑,并利用 `WebMvcConfigurer` 或者扩展类配置多个拦截器形成一条执行链条。 #### 拦截器链的核心概念 拦截器链的主要作用是对 HTTP 请求进行预处理和后处理操作。Spring 提供了灵活的方式让开发者自定义这些行为。例如,在登录验证场景下,可以通过设置不同的拦截器分别完成身份校验、权限控制等功能[^1]。 当存在多层业务需求时(比如先刷新令牌再做其他检查),就需要调整各个拦截器之间的调用顺序。这通常借助 `.order()` 方法设定优先级别来达成目标。数值越低表示该阶段会被更早触发[^2]。 以下是具体代码展示如何构建这样一个机制: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Autowired private RefreshTokenInterceptor refreshTokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { // 设置第一个拦截器及其排除路径规则 registry.addInterceptor(loginInterceptor) .excludePathPatterns("/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login") .order(1); // 添加第二个全局生效的拦截器并赋予最高优先权 registry.addInterceptor(refreshTokenInterceptor).addPathPatterns("/**").order(0); } } ``` 上述片段展示了两个典型例子:一个是针对特定端点放行而其余均需认证;另一个则是无条件适用于全部路由地址却拥有绝对前置地位的情形。 #### 注意事项 为了保证系统的正常运转以及用户体验不受影响,在规划此类结构之前应当充分考虑实际应用场景下的各种可能性。例如某些公共资源可能不需要任何防护措施即可访问,因此要合理运用`.excludePathPatterns()`函数列举例外情况列表。 另外值得注意的是,尽管可以自由排列各组件间的相对位置关系,但过度复杂化可能会带来维护上的困难甚至潜在错误风险。所以建议保持简洁明了的设计思路同时兼顾未来扩展性的预留空间。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值