SpringBoot集成SpirngMVC之拦截器

SpirngMVC中的拦截器

目录

1、概述

SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

过滤器:依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。

拦截器:依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。

2、原理

看下对应的接口:

public interface HandlerInterceptor {
	// 执行handler之前执行
	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 {
	}

}

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

2、先来**顺序执行 **所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

**6、前面的步骤有任何异常都会直接倒序触发 **afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

那么来看下对应的源码操作:

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

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

在执行handler方法之前执行拦截器的前置处理。可以看到如果拦截器的applyPreHandle如果返回的是false,那么就直接返回了,不向下继续执行了,也就是说,不会再来执行handler方法。那么看下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;
	}

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
		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);
			}
		}
	}

看下对应的执行流程:

  • 1、for循环正序遍历,循环遍历每个拦截器来执行preHandle方法;
  • 2、如果拦截器返回的是true,那么记录一下每个拦截器的索引;如果拦截器返回的是FALSE,那么这里将会直接来执行当前拦截器的triggerAfterCompletion方法,然后就直接返回了。也就是说,如果拦截器链(ABC)中的某一个拦截器B执行preHandle方法时返回的是false,那么会直接直接上一下拦截器A中的afterCompletion方法,而B中的post和after以及C中的pre、post和after都不会执行了
  • 如果所有的拦截器都执行成功,那么会继续向下来进行执行

那么这里来记录一下正常的执行流程:

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			....
			try {
				....
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				....
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

上面就是对应的全过程了。那么下面来进行测试一下。

3、例子

这里按照上面的方式来实现三个拦截器来进行响应处理:

创建三个拦截器:

1、OneHandlerInterceptor

public class OneHandlerInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(OneHandlerInterceptor.class);

    /**
     * 返回为true,则会进入到下一个过滤器链中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("one preHandle---------------------");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("one postHandle---------------------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("one afterCompletion---------------------");
    }
}

2、TwoHandlerInterceptor

public class TwoHandlerInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(TwoHandlerInterceptor.class);

    /**
     * 返回为true,则会进入到下一个过滤器链中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("two preHandle---------------------");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("two postHandle---------------------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("two afterCompletion---------------------");
    }
}

3、ThreeHandlerInterceptor

public class ThreeHandlerInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(ThreeHandlerInterceptor.class);

    /**
     * 返回为true,则会进入到下一个过滤器链中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("three preHandle---------------------");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("three postHandle---------------------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("three afterCompletion---------------------");
    }
}

4、配置拦截器

// 如果想要自定义springmvc,那么只需要在容器中添加一个WebMvcConfigurer类型的组件
@Configuration(proxyBeanMethods = false)
public class CustomWebMVCConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new OneHandlerInterceptor()).addPathPatterns("/self/**").excludePathPatterns("/api/**").order(2);
        registry.addInterceptor(new TwoHandlerInterceptor()).addPathPatterns("/self/**").excludePathPatterns("/api/**").order(3);
        registry.addInterceptor(new ThreeHandlerInterceptor()).addPathPatterns("/self/**").excludePathPatterns("/api/**").order(4);
    }
}

每个拦截器有对应的拦截器的路径和对应的排除路径,以及对应的优先级顺序,也就是拦截器的执行顺序,哪个先执行,哪个后执行,都是可以来进行设置的。

5、编写对应的controller

@Controller
@RequestMapping(path = "self")
public class InterceperController {


    @RequestMapping(path = "hello")
    @ResponseBody
    public String hello(){
        return "sucdess";
    }

}

6、访问

http://localhost:8080/web/self/hello

7、输出信息

2022-05-15 17:55:59.220   c.g.s.interceptor.OneHandlerInterceptor  : one preHandle---------------------
2022-05-15 17:55:59.220   c.g.s.interceptor.TwoHandlerInterceptor  : two preHandle---------------------
2022-05-15 17:55:59.220   c.g.s.interceptor.OneHandlerInterceptor  : one afterCompletion---------------

可以看到这里的因为

4、项目应用

1、问题描述

想要记录一下一次从开始到结束阶段的请求处理时间。

在拦截器的preHandle 方法中记录开始时间,在拦截器的afterCompletion 来记录结束时间。

2、应用技术

因为每个web请求都需要有一个对应的hander来进行处理,在每个handler执行之前,都需要经过intecepter来进行过滤处理,从请求之前到请求之后。

但是因为对于每个拦截器来说都是单例的,即线程不安全,所以针对于每个用户来说,每个请求都是一个新的线程,所以应该考虑到线程安全问题。所以需要在拦截器中使用ThreadLocal来记录一下对应的时间。

3、代码实现

public class LogRequestTimeHandlerInterceptor extends HandlerInterceptorAdapter {  
    private NamedThreadLocal<Long>  startTimeThreadLocal =  new NamedThreadLocal<Long>("StopWatch-StartTime");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {  
        long beginTime = System.currentTimeMillis();//1、开始时间  
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)  
        return true;//继续流程  
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {  
        long endTime = System.currentTimeMillis();//2、结束时间  
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)  
        long consumeTime = endTime - beginTime;//3、消耗的时间  
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求  
            //TODO 记录到日志文件  
            System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        }          
    }  
}  

添加到MVC中去

// 如果想要自定义springmvc,那么只需要在容器中添加一个WebMvcConfigurer类型的组件
@Configuration(proxyBeanMethods = false)
public class CustomWebMVCConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogRequestTimeHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/api/**").order(2);
    }
}

相关文章

今天小编给大家分享的是Springboot下使用Redis管道(pipeline...
本篇文章和大家了解一下springBoot项目常用目录有哪些。有一...
本篇文章和大家了解一下Springboot自带线程池怎么实现。有一...
这篇文章主要介绍了SpringBoot读取yml文件有哪几种方式,具有...
今天小编给大家分享的是SpringBoot配置Controller实现Web请求...
本篇文章和大家了解一下SpringBoot实现PDF添加水印的方法。有...