目录
[Spring 拦截器实现](#Spring 拦截器实现)
[Spring 过滤器实现](#Spring 过滤器实现)
引言
- 原生 Spring AOP 实现统一拦截有两个难点
- 难点一:定义拦截规则表达式
- 难点二:在切面中获取到 HttpSession
Spring 拦截器实现
- 为了解决原生 Spring AOP 实现统一拦截的难点
- Spring 提供了具体的实现拦截器 HandlerInterceptor
javaimport org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器:执行 preHandle 方法。"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器:执行 postHandle 方法。"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器:执行 afterCompletion 方法。"); } }
- preHandle 在请求方法执行前被调用,也就是在调用目标方法之前被调用
- postHandle 在请求方法执行后被调用,但是会在 DispatcherServlet 进行渲染视图之前被执行
- afterCompletion 会在整个请求结束之后再执行,也就是在 DispatcherServlet 渲染了对应的视图之后再执行
实例理解
- 此处我们想要实现 用户登录验证 的判断
步骤一
- 创建自定义拦截器,此处创建了一个 LoginInterceptor 类 ,实现 HandlerInterceptor 接口
- 用户登录验证 属于执行目标方法之前就应对其进行判断,所以此处重写 preHandler 方法,并在方法中编写相应的业务代码
javapackage com.example.demo.component; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /* * 自定义拦截器 * */ @Component public class LoginInterceptor implements HandlerInterceptor { /* * 调用目标方法之前执行的方法 * 此方法返回 boolean 类型的值 如果返回的为 true 表示拦截器 验证成功 继续走后续的流程 * 如果返回 false 则表示拦截器 验证未通过 后续的流程和目标方法不要执行了 * */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 用户登录判断业务 HttpSession session = request.getSession(false); if(session != null && session.getAttribute("session_userinfo") != null) { // 用户已经登录 return true; } // 到达此处说明用户未登录 // 我们可以直接重定向到登录页面 // response.sendRedirect("/login.html"); // 此处我们未写登录页面,所以我们直接返回一个 401 表示客户端错误 response.setStatus(401); return false; } }
步骤二
- 将自定义拦截器加入到系统配置
- 设置拦截规则
javapackage com.example.demo.config; import com.example.demo.component.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**")//拦截所有的 url /* 代表一级 /** 代表多级 .excludePathPatterns("/user/login") //排除 url /user/login 不拦截 .excludePathPatterns("/user/register") .excludePathPatterns("/css/**") //排除 css 文件夹下的所有文件 .excludePathPatterns("/js/**") //排除 js 文件夹下的所有文件 .excludePathPatterns("/image/**") //排除 image 文件夹下的所有文件 ; } }
- 设置完拦截器后的交互流程图
Spring 过滤器实现
- 过滤器的实现可以使用 Servlet 3.0 提供的 @WebFilter 注解
- 配置过滤的 URL 规则、实现 Filter 接口 、重写接口中的 doFilter 方法
javapackage com.example.demo.component; 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 方法"); } }
- init 在容器启动时会被调用,整个程序运行期只会被调用一次,用于实现 Filter 对象的初始化
- doFilter 具体的过滤功能实现代码,通过此方法对请求进行过滤处理
- destory 用于 Filter 销毁前完成相关资源的回收工作
注意:
- filterChain.doFilter(servletRequest,servletResponse); 请求放行
- 该行代码的作用是将请求传递给过滤器链中的下一个过滤器或目标资源(执行下一个流程)
- 如果没有这行代码,那么请求将会在当前的过滤器中停止,不会继续向下传递
实例理解
- 此处我们想实现 敏感词过滤
javapackage com.example.demo.component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class SensitiveWordsFilter 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 方法"); // 获取用户提交的信息 String message = servletRequest.getParameter("message"); // 敏感词库 String[] sensitiveWords = {"敏感词1", "敏感词2"}; for (String sensitiveWord : sensitiveWords) { if (message.contains(sensitiveWord)) { message = message.replace(sensitiveWord, "*"); } } servletRequest.setAttribute("message", message); // 请求放行 filterChain.doFilter(servletRequest, servletResponse); System.out.println("过滤器:结束执行 doFilter 方法"); } @Override public void destroy() { System.out.println("过滤器:执行 destroy 方法"); } }
拦截器和过滤器的区别
- 主要有 5 个方面
出身不同
- 拦截器 来自于 Spring 框架
- 过滤器 来自于 Servlet
触发时机不同
- 请求执行顺序为:
- 请求进入容器 ------> 进入过滤器 ------> 进入 Servlet ------> 进入拦截器 ------> 进入控制器(Controller)
底层实现不同
拦截器
- 基于 Java 的 Servlet 规范实现的,通过实现 HandleInterceptor 接口来实现拦截器功能
- 在 Spring Boot 框架的执行流程中,拦截器被注册在 DispatcherServlet 的 doDispatch 方法中,该方法是 Spring Boot 框架的核心方法,用于处理请求和响应
- 程序每次执行时都会调用 doDispatch 方法,并验证拦截器链(一个应用中可以同时存在多个拦截器,每个拦截器均按提前配置好的顺序执行),之后再根据拦截器返回的结果,进行下一步的处理
- 如果返回的是 true 则将继续调用目标方法
- 如果返回的是 false 则直接返回验证失败给前端
过滤器
- 基于方法回调
支持的项目类型不同
- 拦截器 是 Spring 中的一个组件,因此拦截器即可以用在 Web 项目中,同时还可以用在 Application 或 Swing 程序中
- 过滤器 是 Servlet 规范中定义的,所以过滤器要依赖于 Servlet 容器,它只能用在 Web 项目中
使用场景不同
- 拦截器 更接近业务系统,所以拦截器主要用来实现项目中的业务判断,如登录判断、权限判断、日志记录等
- 过滤器 通常是用来实现通用功能过滤的,如敏感词过滤、字符集编码设置、响应数据压缩 等