spring拦截器机制及其使用

1、spring拦截器介绍

spring针对处理器映射器提供了一种拦截器的机制,允许我们自定义一些处理逻辑,比如打印日志、校验用户是否登录等,然后spring会在调用最终的处理器前后执行我们自定义的逻辑。

注:

(1)处理器映射器,也叫HandlerMapping,关于它的作用,可以简单理解为它能根据请求路径找到我们在controller中写的能处理外部请求的方法。

(2)本文所展示的代码基于springboot 2.7.0版本(对应spring 5.3.20版本)

1.1、 拦截器的结构

spring要求我们自定义的拦截器需要实现HandlerInterceptor接口,我们先看下HandlerInterceptor接口内容:

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

简要解释下上面三个方法:

  • preHandle方法

    在目标处理器(controller接口方法)运行之前执行,返回boolean值

  • postHandle方法

    在目标处理器(controller接口方法)运行之后执行

  • afterCompletion方法

    在整个请求完成(页面渲染)之后执行

1.2、拦截器原理

先为大家展示一下spring执行拦截器调用相关的代码,重点就在DispatcherServlet类中

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 第一步,找到请求对应的处理器,并生成处理器链,里面包括处理器信息以及拦截器列表信息
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
               
                // 第二步,通过处理器链调用各个拦截器的preHandler方法,如果返回了false,说明不能执行目标处理器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 第三步,执行目标处理器方法
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
                // 第四步,通过处理器链(倒序)调用各个拦截器的postHandler方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
            // 处理目标处理器返回的数据(进行页面渲染),并倒序调用各个拦截器的afterCompletion方法
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
            // 出现异常时,执行本类中triggerAfterCompletion方法,进而调用mappedHandler.triggerAfterCompletion
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
    
    private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

		if (mappedHandler != null) {
            // 只要处理器链对象不为空,就直接去调用处理器链里面的拦截器列表中已执行拦截器的afterCompletion方法
			mappedHandler.triggerAfterCompletion(request, response, ex);
		}
		throw ex;
	}

上面代码块中提到的mappedHandler的applyPreHandle、applyPostHandle和triggerAfterCompletion方法位于HandlerExecutionChain类,实现如下:

HandlerExecutionChain类

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			// 此下标记录了当前拦截器执行到了哪一个,方便倒序执行拦截器的afterCompletion方法
			this.interceptorIndex = i;
		}
		return true;
	}
	
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
	    // applyPreHandle方法记录了已经执行了preHandle方法的拦截器在拦截器列表中的下标,也即interceptorIndex的值
		for (int i = this.interceptorIndex; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
	
	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
        // 倒序执行所有拦截器的postHandle方法
		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}

简单总结下spring拦截器的原理:

  1. DispatcherServlet根据当前的请求找到执行器链(内部封装了目标处理器,以及目标处理器拥有的拦截器列表)
  2. 首先会顺序执行拦截器链中的各个拦截器的preHandle方法,这时候会有两种情况

    • 每个拦截器的preHandle都返回了true,所有处理器的preHandle都会得到执行,然后进入第3步
    • 如果执行到某个拦截器的preHandle方法返回了false,就会倒序执行所有已经执行过的拦截器的afterCompletion方法,并且请求会直接结束,不会再执行后续的操作
  3. 所有的拦截器都返回了true,则执行目标方法(接口)

  4. 接着就是倒序执行所有拦截器的postHandle方法

  5. 然后就是渲染页面,并倒序执行所有拦截器的afterCompletion方法

  6. 当上面的第2步和第5步之间的任何一步出了异常,也都会倒序执行所有拦截器的afterCompletion方法

2、拦截器的使用

我们可以自定义一个拦截器,针对未登录用户的请求进行认证拦截。

2.1、定义拦截器

实现思路:

  • 定义LoginInterceptor拦截器,实现HandlerInterceptor接口。
  • 正常情况下,用户登录成功后,会将用户信息保存到session中,如果通过用户的请求,无法在session中查询到用户信息,说明用户未登录。
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        UserDTO user = (UserDTO) session.getAttribute("USER");
        //session没有用户信息,说明用户未登录,阻止继续处理用户的请求,直接返回false
        if(user == null) {
            log.warn("用户未登录,不予处理请求");
            return false;
        }
        return true;
    }
}

UserDTO的信息如下:

public class UserDTO {

    private String name;

    private String userId;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
2.2、注册拦截器到容器

定义WebMvcConfig配置类,实现WebMvcConfigurer接口

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private LoginInterceptor loginInterceptor;

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

结束语

本文先分享到这里,觉得有收获的朋友,可以关注我,或者进行分享或收藏,有疑惑的也可以来私聊评论,我会及时进行回复~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值