目录

前言:

1. 为什么项目Controller的接口方法没有进入拦截器的preHandle?

2. 拦截器定义

3.如果preHandle返回为false,接下来会执行什么?

4. 多拦截器处理顺序?

5. 理论知识

5.1 prehandle

5.2 拦截器的基本概念?

5.3 什么是servlet容器?

5.4 什么是过滤器?

5.4 拦截器与过滤器的区别

5.5 监听器

5.6 拦截器与过滤器的使用场景


前言:

最近总是整合和联系搭建项目,所以根据不同的需求有着不同的拦截器的写法,以及有的需求会有多个拦截器。

而拦截器用于 日志记录、用户登录状态拦截、安全拦截等等。

我的感情 我的人性 穿越世纪

你的记忆 你的秘密 依然清晰

我的回忆 我的思绪 不停累积

有你在一起 你是专属唯一

                                                                                                                                                           ——《Wonderland》JJ 

1. 为什么项目Controller的接口方法没有进入拦截器的preHandle?

原因是因为拦截器的配置还要进行注入。

拦截器也是需要代码中加入进去的。类似于这样。

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private RequestInterceptor requestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //本项目把所有的路径都加入到拦截的请求中,根据需求可以具体来添加内容
        registry.addInterceptor(requestInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }

    /**
     * 继承前端代码
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        super.addResourceHandlers(registry);
    }
}

2. 拦截器定义

package com.switcher.common.intercepter;

import com.switcher.common.constant.ErrorConstants;
import com.switcher.common.constant.SystemConstants;
import com.switcher.data.Result;
import com.switcher.utils.FastJsonUtils;
import com.switcher.utils.ThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import 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;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Description : RequestInterceptor
 * 拦截处理类
 * <p>
 *  preHandle按拦截器定义顺序调用
 *  postHandler按拦截器定义逆序调用
 *  afterCompletion按拦截器定义逆序调用
 *  postHandler在拦截器链内所有拦截器返成功调用
 *  afterCompletion只有preHandle返回true才调用
 * </p>
 * <p>
 *     支持异步线程方法中可能取不到值
 * @Date : 2019/10/12
 * @Author : pmdream
 */
@Component
@Slf4j
public class RequestInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 将请求初始参数存入threadLocal
        ThreadLocalUtil.setIsCacheFlag(false);
        //是否需要cache
        ThreadLocalUtil.setNeedCacheFlag(false);
        ThreadLocalUtil.setResponse(response);
        ThreadLocalUtil.setStartTime(System.currentTimeMillis());
        //把请求中的globalRequestId 存入
        String globalRequestId = request.getHeader(SystemConstants.REQUEST_ID);
        ThreadLocalUtil.setRequestId(globalRequestId);
        String userId = request.getHeader(SystemConstants.USER_ID);
        ThreadLocalUtil.setUserId(userId);
        String token = request.getHeader(SystemConstants.REQUEST_TOKEN_KEY);
        if (!SystemConstants.REQUEST_TOKEN_VALUE.equals(token)) {
            log.info("token验证异常! GlobalRequestId = {}", globalRequestId);
            PrintWriter writer = null;
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            try {
                writer = response.getWriter();
                Result result = new Result(ErrorConstants.TOKEN_ERROR_MSG, ErrorConstants.TOKEN_ERROR_CODE, null, null);
                writer.print(FastJsonUtils.toJsonString(result));
                return false;
            } catch (IOException e) {
                log.error("response返回响应信息异常!", e);
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        long totalTime = ThreadLocalUtil.getTotalTime();
        log.info("===time===请求处理结束,总耗费时间:{}ms, 请求GlobalRequestId = {}", totalTime, ThreadLocalUtil.getRequestId());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        ThreadLocalUtil.remove();
    }

}

3.如果preHandle返回为false,接下来会执行什么?

通过代码调试,发现如果返回false的话,是不会在进入postHandle 和afterCompletion

相当于预处理就已经出错了。

正常流程,pre - post -after

但是如果返回false 的话,那么就会直接在pre里面处理掉了。

当它返回为false 时,表示请求结束,后续的Interceptor 和Controller都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

那么如果是fasle,进入底层断点一下,再看一下底层的执行逻辑。

4. 多拦截器处理顺序?

一般来说,可以写两个拦截器或者更多拦截器,对于请求的URI不同,

然后进行不同的拦截器。

当然,如果有多个拦截器对一个请求路径呢?执行的顺序会是怎么样?

https://blog.csdn.net/syslbjjly/article/details/90675223

所以,正常放行情况

那么如果是异常情况呢?

拦截器都是链式的执行的,如果第一个pre就返回false,是不会进入第二个拦截器的。

-----如果第一个为true,第二个为false(只要有一个pre为false 也就是拦截器不放行,那么post就不会执行)

但是正常执行的拦截器,也就是放行的拦截器,是一定要执行afterCompletion()的。

已经自己校验过,图的话取得是上面链接博主的。

5. 理论知识

5.1 prehandle

SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法

5.2 拦截器的基本概念?

拦截器不依赖与Servlet容器,依赖于Spring等web框架。

拦截器是一种AOP的应用,底层是用的是Java反射机制来实现的。

于过滤器的很大区别是拦截器可以注入Spring的bean,这个通过上面的代码就可以看出来了。

拦截器在springboot的配置中就是可以用@Bean 注解 或者@Autowired 来进入。

拦截器与过滤器最大的区别可能是拦截器注入到Spring的 bean,能够获取到各种需要的Service来处理业务逻辑,

而过滤器不行。

拦截器可以继承HandlerInterceptorAdapter 或者HandlerInterceptor来实现。

另外,MVC的项目中的配置:

在spring-mvc的xml文件中,大体是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <import resource="classpath:view/view.xml"/>

    <import resource="classpath:security/security-mvc.xml"/>

    <mvc:annotation-driven/>

    <mvc:interceptors>
        <bean class="xxx.web.common.ApiLogInterceptor"/>
    </mvc:interceptors>

    <mvc:interceptors>
        <bean class="xxx.web.common.CORSInterceptor"/>
    </mvc:interceptors>

    <mvc:interceptors>
        <bean class="xxx.web.common.LoginInterceptor"/>
    </mvc:interceptors>

    <mvc:interceptors>
        <bean class="xxx.web.common.RequestLimitsInterceptor"/>
    </mvc:interceptors>

    <!-- 加载Controller -->
    <context:component-scan base-package="xxx" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

所以,springboot还是简化了不少的东西。

拦截器的执行顺序:如果MVC的话语web.xml 里面配置的顺序有关,链式执行。

如果是springboot的话,那么就是在webMvc的配置文件中,注册的先后顺序。

PS:这里可能会被问到@Autowired到底是什么,相当于什么样的过程。

也可能面试官会引出AOP的相关概念,之后再补充。

5.3 什么是servlet容器?

https://blog.csdn.net/yw_1207/article/details/78706701

5.4 什么是过滤器?

Filter,依赖于Servlet容器,配置到web.xml文件中。也可以配置多个,执行的顺序是按照配置顺序的从上到下。

常用来配置一些请求编码和过滤一些非法参数,垃圾信息,或者是网站登录的验证码。

(主要总结   : 依赖容器,过滤非法参数和编码。)

在我们的老的项目中,配置是这样的:

<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

不过只看配置文件,大体能看出,拦截器的配置是需要mapping的,也就是针对什么URI来进行不同的拦截,默认的话可以对全部路径进行拦截。

然后看拦截器是指定了编码格式,比如UTF-8。

一种实现:(来自博主:https://blog.csdn.net/javageektech/article/details/94250382

大体上过滤器还是用来验证登录验证码用的,我们项目都没有配置过滤器,因为登录比较简单。

public class CaptchaFilter implements Filter {    public void init(FilterConfig config) throws ServletException {    }    public void destroy() {    }    public void doFilter(ServletRequest servletRequest,            ServletResponse servletResponse, FilterChain filterChain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) servletRequest;        HttpServletResponse response = (HttpServletResponse) servletResponse;        String servletPath = request.getServletPath();        //获取验证码        if(servletPath.matches("/captcha.jpg")) {            response.setContentType("image/jpeg");            //禁止图像缓存。            response.setHeader("Pragma", "no-cache");            response.setHeader("Cache-Control", "no-cache");            response.setDateHeader("Expires", 0);            //参数:宽、高、字符数、干扰量            CaptchaProductor vCode = new CaptchaProductor(70,30,4,75);            //根据token保存验证码内容            CaptchaBean bean = new CaptchaBean();            bean.setCaptcha(vCode.getCode());            bean.setCreateTime(new Date());            HttpSessionUtils.setSessionValue(request, "sessionCaptcha", bean);            vCode.write(response.getOutputStream());            return;        }    }}class CaptchaFilter implements Filter {


    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    public void doFilter(ServletRequest servletRequest,
            ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String servletPath = request.getServletPath();
        //获取验证码
        if(servletPath.matches("/captcha.jpg")) {
            response.setContentType("image/jpeg");
            //禁止图像缓存。
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            //参数:宽、高、字符数、干扰量
            CaptchaProductor vCode = new CaptchaProductor(70,30,4,75);

            //根据token保存验证码内容
            CaptchaBean bean = new CaptchaBean();
            bean.setCaptcha(vCode.getCode());
            bean.setCreateTime(new Date());
            HttpSessionUtils.setSessionValue(request, "sessionCaptcha", bean);
            vCode.write(response.getOutputStream());
            return;
        }
    }
}

过滤器的实现可以通过实现 Filter 接口或者继承 Spring 的org.springframework.web.filter.OncePerRequestFilter 来实现。

5.4 拦截器与过滤器的区别

过滤器就是筛选出你要的东西,比如requeset中你要的那部分(筛选)
拦截器在做安全方面用的比较多,比如终止一些流程(请求进入的再次拦截)

过滤器:

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

拦截器:

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架;
在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用;
缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理;
由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。

总结:

拦截器是基于java的反射机制的,而过滤器是基于函数回调。

拦截器不依赖与servlet容器,过滤器依赖与servlet容器。

拦截器只能对action请求起作用(进入controller的),而过滤器则可以对几乎所有的请求起作用(包括静态页面)。

拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。

在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

5.5 监听器

监听器的作用是监听一些事件的发生从而进行一些操作,比如监听ServletContext,HttpSession的创建,销毁,从而执行一些初始化加载配置文件的操作,

当Web容器启动后,Spring的监听器会启动监听,监听是否创建ServletContext的对象,如果发生了创建ServletContext对象这个事件(当web容器启动后一定会生成一个ServletContext对象,所以监听事件一定会发生),ContextLoaderListener类会实例化并且执行初始化方法,将spring的配置文件中配置的bean注册到Spring容器中,

监听的操作是读取WEB-INF/applicationContext.xml,但是我们可以在web.xml中配置多个需要读取的配置文件,如下方所示,读取完成后所有的配置文件中的bean都会注册到spring容器中。

比如监听 ServletContext对象:

public void contextInitialized(ServletContextEvent event);  
public void contextDestoryed(ServletContextEvent event);  

监听HttpSessionListener 对象:

publicvoid sessionCreated(HttpSessionEvent event);   
publicvoid sessionDestoryed(HttpSessionEvent event);  

监听HttpRequestListener 对象:

public void requestinitialized(ServletRequestEvent event);  
public void requestDestoryed(ServletRequestEvent event);  

web.xml的文件配置:

<listener>
		<listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
	</listener>
	<!--<listener>-->
		<!--<listener-class>-->
			<!--org.springframework.web.util.IntrospectorCleanupListener</listener-class>-->
	<!--</listener>-->

	<!-- 加载通用的bean -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:xxx-servlet.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
	</listener>
	<listener>
		<listener-class>
			org.springframework.web.context.request.RequestContextListener
		</listener-class>
	</listener>

上述配置提到的:HttpSessionEventPublisher 和ContextLoaderListener 就是监听器的一部分功能。

比如mvc项目自己定义监听器:

<listener>  
        <listener-class>listener.MyListener</listener-class>  
    </listener>  

5.6 拦截器与过滤器的使用场景

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

1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。

2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;

3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);(项目中拦截器记录了时间)

4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。

5、OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。
(这个暂时没用到)

拦截器是AOP的一种实现,底层通过动态代理模式完成。(动态代理面试必问)

转自:https://www.cnblogs.com/lwh0206/p/7170029.html

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐