过滤器拦截器,Interceptor+Filter 理解

发布时间 2023-08-02 17:53:21作者: 追风fc

在我们springMVC项目中,一个客户端请求到达DispatcherServlet前会依次经过过滤器(Filter)和拦截器(Interceptor),所以有必要了解下过滤器和拦截器分别做了啥以及项目中该如何配置。

 
一. Filter
package javax.servlet
 
1. 三个方法
1.1 init(): 初始化参数,在创建Filter调用,当我们需要设置初始化参数可以写到该方法中。
1.2 doFilter(): 拦截到要执行的请求时调用,写我们对请求和响应的处理。如果有多个过滤器,则过滤器链继续调doFilter()方法。
1.3 destroy(): 在销毁Filter时调用。
 
2. 生命周期
    Filter的创建和销毁由web服务器控制。服务器启动的时候,web服务器创建Filter的实例对象,并调用其init方法,完成对象的初始化功能。filter对象只会创建一次。服务器关闭时,web服务器销毁Filter的实例对象。
 
3. 作用
Filter和Interceptor都可以用来做权限校验,参数校验,限流等前处理。但一般都放在拦截器里做,过滤器这里我们常用的就是设置字符集编码。
 
4. 写法
通常有配置文件写法和注解写法,这里只介绍注解写法。
4.1 springboot启动类上加@ServletComponentScan注解
 
4.2 自定义过滤器,实现Filter,重写doFilter方法
加@WebFilter注解,设置配置,如配置匹配的url(/* 表示所有的请求都需要经过该过滤器)
我举例里定义了两个过滤器,主要为了看下执行顺序
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter doFilter before filter");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("MyFilter doFilter after filter");
}

@Override
public void destroy() {
}
}
 
@WebFilter(urlPatterns = "/*")
public class MyFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter2 doFilter before filter");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("MyFilter2 doFilter after filter");
}

@Override
public void destroy() {

}
}
 
这时如果有请求过来,那么执行的顺序为
MyFilter doFilter before filter
MyFilter2 doFilter before filter
执行业务逻辑
MyFilter2 doFilter after filter
MyFilter doFilter after filter
 
可以看到doFiliter里有个参数FilterChain,这是设计模式中责任链模式的一种变形使用。首先一个请求有多个多虑器处理对象,这时可以将所有的过滤器都初始化到过滤器链当中,当当前的过滤器执行完业务后,直接丢到责任链当中,由责任链维护接下去的执行流程。
 
二. 拦截器
包:org.springframework.web.servlet
1. 三个方法
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():业务处理前被调用,同过滤器一样,一般用在权限校验,参数校验,限流等预处理
postHandle():在业务处理器处理请求执行完成后,生成视图之前执行
afterCompletion():DispatcherServlet完全处理完请求后被调用,可用于清理资源等
一般来说,实际业务场景中,我们只需要重写preHandle()方法就够了。
 
2. 写法
有配置写法和注解写法,这里与时俱进,仅介绍注解写法。
//自定义拦截器,要结合MyWebConfig使用
@Component
public class CookieInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(CookieInterceptor.class);

/**
* 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("CookieInterceptor preHandle");
// String requestURI = request.getRequestURI();
// Cookie[] cookies = request.getCookies();
// //1.如果是登录接口,则设置cookie
// if (requestURI.contains("/loginInfo/login")) {
// response.addCookie(new Cookie("hasLogined", "true"));
// //2.如果不是登录接口,则校验有无cookie
// } else {
// boolean flag = false;
// if (cookies != null) {
// for (Cookie cookie : cookies) {
// if (cookie.getName().equals("hasLogined")) {
// flag = true;
// break;
// }
// }
// }
// if (!flag) {
// LOGGER.error("please login first");
// return false;
// }
// }
return true;
}

/**
* 在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView ;
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView)
{
System.out.println("MyInterceptor postHandle.");
}

/**
* 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
{
System.out.println("MyInterceptor afterCompletion.");
}
}
@Component
public class SessionInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionInterceptor.class);

private Set<String> sessionIds = Sets.newHashSet();
/**
* 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SessionInterceptor preHandle");
// String requestURI = request.getRequestURI();
// //1.如果是登录接口,则设置session
// if (requestURI.contains("/loginInfo/login")) {
// // 用户登陆时如果携带了sessionid,则先将session做invalid处理
// HttpSession session = request.getSession(false);
// if (null != session) {
// session.invalidate();
// }
// // ture,则如果获取不到session,会生成一个;因为上面已失效,所以这里会新生成一个
// session = request.getSession(true);
// String sessionId = session.getId();
// System.out.println("preHandle sessionId=" + sessionId);
// //可以将sessionID存到数据库,redis,内存中;分布式场景中一般会存到redis;此处存在内存中
// sessionIds.add(sessionId);
//
// //额外业务
// InfoVO infoVO = new InfoVO();
// infoVO.setName("libai");
// session.setAttribute("myKey",infoVO);
// } else {
// String sessionId = request.getSession().getId();
// System.out.println("request sessionId = " + sessionId);
//
// if (!sessionIds.contains(sessionId)) {
// LOGGER.error("please login first");
// return false;
// }
//
// //额外业务
// HttpSession session = request.getSession();
// InfoVO myKey =(InfoVO) session.getAttribute("myKey");
// System.out.println(myKey);
// }
return true;
}

/**
* 在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView ;
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView)
{
System.out.println("MyInterceptor2 postHandle.");
}

/**
* 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
{
System.out.println("MyInterceptor2 afterCompletion.");
}
}
@Configuration
public class MyWebConfig implements WebMvcConfigurer {

@Autowired
private CookieInterceptor cookieInterceptor;

@Autowired
private SessionInterceptor sessionInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(cookieInterceptor)
.addPathPatterns("/**/loginInfo/**")
.order(1);

registry.addInterceptor(sessionInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/loginInfo/**", "/RM/Login")
.order(2);
}
}
这时有匹配的请求过来的,执行顺序如下
MyFilter doFilter before filter
MyFilter2 doFilter before filter
CookieInterceptor preHandle
SessionInterceptor preHandle
执行业务逻辑
MyFilter2 doFilter after filter
MyFilter doFilter after filter
 
order越小越靠前
 
三. 总结
两者的包不同,filter属于servlet,interceptor属于MVC框架
执行顺序过滤器在前,拦截器在后
过滤器能做的,拦截器也能做,并且更加灵活,比如可以在业务执行前,执行后处理。一般的预处理会选择在拦截器里执行