Spring -- 拦截器

23 篇文章 0 订阅

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 今天你敲代码了吗


拦截器是Spring框架提供的核心功能,主要用来拦截用户的请求,在指定方法前后执行预先设定的代码(如判断用户是否登录等)image.png

以图书管理系统作为案例展示:
有一个登录接口:
image.png
以及图书信息的相关接口:
image.png
希望能做到,在用户直接访问图书信息接口的时候,进行拦截,判定用户是否登录,如果已经登录则放行,未登录则拦截,跳转到登录页面

1. 自定义拦截器

实现HandlerInterceptor接口,并重写所有方法

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截:检验用户是否登录");
        HttpSession session = request.getSession();
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_INFO_SESSION_KEY);
        if (userInfo == null) {
            log.info("校验失败");
            response.setStatus(401);
            response.getOutputStream().write("noLogin".getBytes());
            return false;
        }
        log.info("校验成功");
        return true;
    }
}

2. 注册配置拦截器

实现WebMvcConfigurer接口,并重写addInterceptor方法

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义的拦截对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    //addPath--添加拦截对象
    //excludePath -- 排除拦截对象(如登录接口不能被拦截
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").
        excludePathPatterns("/user/login").
        excludePathPatterns("/css/**").
        excludePathPatterns("/js/**").
        excludePathPatterns("/imgs/**").
        excludePathPatterns("/**/*.html").excludePathPatterns("/**/*.ico");
    }
}

当我们直接访问图书相关接口的时候:
image.png
就能起到拦截效果

3. 拦截器详解

3.1 拦截路径

拦截路径用来定义拦截器对哪些请求生效
通过addPathPatterns()方法指定要拦截哪些请求,通过 excludePathPatterns()指定哪些请求不拦截

拦截路径含义举例
/*一级路径能匹配/user, /book,但是不能匹配/user/login
/**任意级路径能匹配/user, /use/login
/book/*/book下的一级路径能匹配/book/addBook,
不能匹配/book/addBook/add
/book/**/book下的任意级路径能匹配/book/addBook,
也能匹配/book/addBook/add

3.2 拦截器执行流程
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("目标方法前执行....");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("目标方法后执行....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("视图渲染完后执行....");
    }
}

访问后:
image.png

  • preHandle():目标方法执行前执行,返回true则继续执行后续操作;返回false则中断后续操作
  • postHandle():目标方法执行后执行
  • afterCompletion():视图渲染完后执行,最后执行(现在前后端分离后基本不涉及了)

3.3 DispatcherServlet源码分析

当Tomcat启动的时候,有一个核心的类DispatcherServlet,用来控制程序的执行顺序
image.png
所有请求都会先进到DispatcherServlet,执行doDisPatch调度方法

3.3.1 初始化:

DispatcherServlet的初始化方法init是在其父类FrameworkServlet的父类HttpServletBean中实现的,源码如下:

@Override
public final void init() throws ServletException {

// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
    try {
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        if (logger.isErrorEnabled()) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        }
        throw ex;
    }
}

// Let subclasses do whatever initialization they like.
initServletBean();
}

实际上这个方法就是在初始化Servlet,包括初始化DispatcherServlet
在这个方法的最后,调用了initServletBean方法,是在FrameworkServlet里面实现的
主要的作用就是建立WebApplicationContext容器(即上下文),用来存储Bean

@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
    logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();

try {
    this.webApplicationContext = initWebApplicationContext();
    initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
    logger.error("Context initialization failed", ex);
    throw ex;
}

if (logger.isDebugEnabled()) {
    String value = this.enableLoggingRequestDetails ?
    "shown which may lead to unsafe logging of potentially sensitive data" :
    "masked to prevent unsafe logging of potentially sensitive data";
    logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                 "': request parameters and headers will be " + value);
}

if (logger.isInfoEnabled()) {
    logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}

我们平时启动项目自动打印的日志,有一部分就是这里实现的(这一部分是在接受请求后才能打印出来的)
image.png
在这个方法内,调用了initWebApplicationContext方法初始化Bean容器的过程中,调用了onRefresh方法,用来初始化SpringMVC的容器,就是存储的Controller之类的很多Bean

3.3.2 处理请求

此时接受到的请求是在DispatcherServlet.doDispatch进行处理的

@SuppressWarnings("deprecation")
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);

            // Determine handler for the current 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;
                }
            }

            //执行拦截器的preHandle方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            //执行目标方法
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

            applyDefaultViewName(processedRequest, mv);

            //执行拦截器的postHandle方法
            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 ServletException("Handler dispatch failed: " + err, err);
        }

        //处理视图 在这个方法里面执行拦截器的afterCompletion方法
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        //捕获异常也要执行拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
         //捕获异常也要执行拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new ServletException("Handler processing failed: " + err, 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);
          }
       }
    }
}

我们来看applyPreHandle方法:

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;
        }
        this.interceptorIndex = i;
    }
    return true;
}

此时就会获取到所有的拦截器,执行拦截器里面的preHand方法

3.3.3 适配器

HandlerAdapter主要用于支持不同类型的处理器,在Spring MVC中,处理器有很多种,如基于JavaBean的控制器,基于注解的控制器(如@Controller)等等,每个控制器可能需要不同的方式来调用,为了让他们能够适配统一的请求流程,就使用了HandlerAdapter,这样Spring MVC就能通过一个统一的接口来处理来自各种控制器的请求
这就涉及到设计模式之一 – 适配器模型的思想


感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 今天记得敲代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值