Java web项目自定义多个拦截器的执行顺序,以及拦截器的作用
目录前言:1. 为什么项目Controller的接口方法没有进入拦截器的preHandle?2. 拦截器定义3.如果preHandle返回为false,接下来会执行什么?4. 多拦截器处理顺序?5. 理论知识5.1 prehandle5.2 拦截器的基本概念?5.3 什么是servlet容器?5.4 什么是过滤器?5.4 拦截器与过滤器的区别5.5 ...
目录
1. 为什么项目Controller的接口方法没有进入拦截器的preHandle?
3.如果preHandle返回为false,接下来会执行什么?
前言:
最近总是整合和联系搭建项目,所以根据不同的需求有着不同的拦截器的写法,以及有的需求会有多个拦截器。
而拦截器用于 日志记录、用户登录状态拦截、安全拦截等等。
我的感情 我的人性 穿越世纪
你的记忆 你的秘密 依然清晰
我的回忆 我的思绪 不停累积
有你在一起 你是专属唯一
——《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的一种实现,底层通过动态代理模式完成。(动态代理面试必问)
更多推荐
所有评论(0)