黑马点评系列
基于黑马点评的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 点不同:出身不同、触发时机不同、实现不同、支持的项目类型不同以及使用的场景不同。过滤器通常是用来进行全局过滤的,而拦截器是用来实现某项业务拦截的。

1354

被折叠的 条评论
为什么被折叠?



