参考文献

过滤器(Filter)

  • 作用:过滤器是处于客户端和服务器资源文件之间的一道过滤网,帮助我们过滤掉一些不符合要求的请求,通常用作Session校验,判断用户权限,如果不符合设定条件,则会被拦截到特殊的地址或者基于特殊的响应。

过滤器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.holelin.sundry.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

/**
* @Description:
* @Author: HoleLin
* @CreateDate: 2022/7/23 15:03
* @UpdateUser: HoleLin
* @UpdateDate: 2022/7/23 15:03
* @UpdateRemark: 修改内容
* @Version: 1.0
*/
@Slf4j
@Component
public class TestFilter implements Filter {
/**
* 在容器中创建当前过滤器的时候自动调用
* 它在 Filter 的整个生命周期只会被调用一次。
* 「注意」:这个方法必须执行成功,否则过滤器会不起作用。
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
log.info("Filter 前置");
}

/**
* 过滤的具体操作
* 容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。
*
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
log.info("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);

}

/**
* 在容器中销毁当前过滤器的时候自动调用
* 在过滤器 Filter 的整个生命周期也只会被调用一次
*/
@Override
public void destroy() {
Filter.super.destroy();
log.info("Filter 后置");

}
}

拦截器 (Interceptor)

  • Java中的拦截器是动态拦截 action 调用的对象,然后提供了可以在 action 执行前后增加一些操作,也可以在 action 执行前停止操作,功能与过滤器类似,但是标准和实现方式不同。
    • 登录认证:在一些应用中,可能会通过拦截器来验证用户的登录状态,如果没有登录或者登录失败,就会给用户一个友好的提示或者返回登录页面,当然大型项目中都不采用这种方式,都是调单点登录系统接口来验证用户。
    • 记录系统日志:我们在常见应用中,通常要记录用户的请求信息,比如请求 ip,方法执行时间等,通过这些记录可以监控系统的状况,以便于对系统进行信息监控、信息统计、计算 PV、性能调优等。
    • 通用处理:在应用程序中可能存在所有方法都要返回的信息,这是可以利用拦截器来实现,省去每个方法冗余重复的代码实现。

拦截器的使用

  • 需要实现HandlerInterceptor类,并且重写三个方法
    • preHandle:在 Controoler 处理请求之前被调用,返回值是 boolean类型,如果是true就进行下一步操作;若返回false,则证明不符合拦截条件,在失败的时候不会包含任何响应,此时需要调用对应的response返回对应响应。
    • postHandler:在Controoler处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。
    • afterCompletion:在DispatcherServlet完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.holelin.sundry.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description:
* @Author: HoleLin
* @CreateDate: 2022/7/23 15:09
* @UpdateUser: HoleLin
* @UpdateDate: 2022/7/23 15:09
* @UpdateRemark: 修改内容
* @Version: 1.0
*/
public class TestInterceptor implements HandlerInterceptor {
/**
* 这个方法将在请求处理之前进行调用。
* 「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}

/**
* 只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。
* 「有意思的是」:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,
* 先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

/**
* 只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后,
* DispatcherServlet 渲染了对应的视图之后执行。
* @param request current HTTP request
* @param response current HTTP response
* @param handler the handler (or {@link HandlerMethod}) that started asynchronous
* execution, for type and/or instance examination
* @param ex any exception thrown on handler execution, if any; this does not
* include exceptions that have been handled through an exception resolver
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
  • 将自定义好的拦截器处理类进行注册,并通过addPathPatternsexcludePathPatterns等属性设置需要拦截或需要排除 URL

监听器(Listener)

  • 监听器通常用于监听 Web 应用程序中对象的创建、销毁等动作的发送,同时对监听的情况作出相应的处理,最常用于统计网站的在线人数、访问量等。

    监听器大概分为以下几种:

    • ServletContextListener:用来监听 ServletContext 属性的操作,比如新增、修改、删除。
    • HttpSessionListener:用来监听 Web 应用种的 Session 对象,通常用于统计在线情况。
    • ServletRequestListener:用来监听 Request 对象的属性操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.holelin.sundry.listener;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description:
* @Author: HoleLin
* @CreateDate: 2022/7/23 15:16
* @UpdateUser: HoleLin
* @UpdateDate: 2022/7/23 15:16
* @UpdateRemark: 修改内容
* @Version: 1.0
*/
@Slf4j
public class MyHttpSessionListener implements HttpSessionListener {

public static AtomicInteger userCount = new AtomicInteger(0);

@Override
public synchronized void sessionCreated(HttpSessionEvent se) {
userCount.getAndIncrement();
se.getSession().getServletContext().setAttribute("sessionCount", userCount.get());
log.info("【在线人数】人数增加为:{}",userCount.get());
//此处可以在ServletContext域对象中为访问量计数,然后传入过滤器的销毁方法
//在销毁方法中调用数据库入库,因为过滤器生命周期与容器一致
}

@Override
public synchronized void sessionDestroyed(HttpSessionEvent se) {
userCount.getAndDecrement();
se.getSession().getServletContext().setAttribute("sessionCount", userCount.get());
log.info("【在线人数】人数减少为:{}",userCount.get());
}
}

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.holelin.sundry.config;

import com.holelin.sundry.filter.TestFilter;
import com.holelin.sundry.interceptor.TestInterceptor;
import com.holelin.sundry.listener.MyHttpSessionListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* @Description:
* @Author: HoleLin
* @CreateDate: 2022/7/23 15:14
* @UpdateUser: HoleLin
* @UpdateDate: 2022/7/23 15:14
* @UpdateRemark: 修改内容
* @Version: 1.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
TestInterceptor testInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(testInterceptor).addPathPatterns("/**");
}

/**
* 注册过滤器
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new TestFilter());
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}

/**
* 注册监听器
* @return
*/
@Bean
public ServletListenerRegistrationBean registrationBean(){
ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean();
registrationBean.setListener(new MyHttpSessionListener());
return registrationBean;
}
}

拦截器与过滤器的区别

实现原理不同

  • 过滤器和拦截器底层实现方式大不相同,过滤器 是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。

  • 自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

  • 而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChaindoFilter() 方法,以此循环执行实现函数回调。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    package org.apache.catalina.core;
    // import 略
    /**
    * Implementation of <code>javax.servlet.FilterChain</code> used to manage
    * the execution of a set of filters for a particular request. When the
    * set of defined filters has all been executed, the next call to
    * <code>doFilter()</code> will execute the servlet's <code>service()</code>
    * method itself.
    *
    * @author Craig R. McClanahan
    */
    public final class ApplicationFilterChain implements FilterChain {

    /**
    * Invoke the next filter in this chain, passing the specified request
    * and response. If there are no more filters in this chain, invoke
    * the <code>service()</code> method of the servlet itself.
    *
    * @param request The servlet request we are processing
    * @param response The servlet response we are creating
    *
    * @exception IOException if an input/output error occurs
    * @exception ServletException if a servlet exception occurs
    */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if( Globals.IS_SECURITY_ENABLED ) {
    final ServletRequest req = request;
    final ServletResponse res = response;
    try {
    java.security.AccessController.doPrivileged(
    (java.security.PrivilegedExceptionAction<Void>) () -> {
    internalDoFilter(req,res);
    return null;
    }
    );
    } catch( PrivilegedActionException pe) {
    Exception e = pe.getException();
    if (e instanceof ServletException) {
    throw (ServletException) e;
    } else if (e instanceof IOException) {
    throw (IOException) e;
    } else if (e instanceof RuntimeException) {
    throw (RuntimeException) e;
    } else {
    throw new ServletException(e.getMessage(), e);
    }
    }
    } else {
    internalDoFilter(request,response);
    }
    }

    private void internalDoFilter(ServletRequest request,
    ServletResponse response)
    throws IOException, ServletException {

    // Call the next filter if there is one
    if (pos < n) {
    // 获取pos位置的filter
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
    Filter filter = filterConfig.getFilter();

    if (request.isAsyncSupported() && "false".equalsIgnoreCase(
    filterConfig.getFilterDef().getAsyncSupported())) {
    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
    }
    if( Globals.IS_SECURITY_ENABLED ) {
    final ServletRequest req = request;
    final ServletResponse res = response;
    Principal principal =
    ((HttpServletRequest) req).getUserPrincipal();

    Object[] args = new Object[]{req, res, this};
    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
    } else {

    filter.doFilter(request, response, this);
    }
    } catch (IOException | ServletException | RuntimeException e) {
    throw e;
    } catch (Throwable e) {
    e = ExceptionUtils.unwrapInvocationTargetException(e);
    ExceptionUtils.handleThrowable(e);
    throw new ServletException(sm.getString("filterChain.filter"), e);
    }
    return;
    }

    // We fell off the end of the chain -- call the servlet instance
    try {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
    lastServicedRequest.set(request);
    lastServicedResponse.set(response);
    }

    if (request.isAsyncSupported() && !servletSupportsAsync) {
    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
    Boolean.FALSE);
    }
    // Use potentially wrapped request from this point
    if ((request instanceof HttpServletRequest) &&
    (response instanceof HttpServletResponse) &&
    Globals.IS_SECURITY_ENABLED ) {
    final ServletRequest req = request;
    final ServletResponse res = response;
    Principal principal =
    ((HttpServletRequest) req).getUserPrincipal();
    Object[] args = new Object[]{req, res};
    SecurityUtil.doAsPrivilege("service",
    servlet,
    classTypeUsedInService,
    args,
    principal);
    } else {
    servlet.service(request, response);
    }
    } catch (IOException | ServletException | RuntimeException e) {
    throw e;
    } catch (Throwable e) {
    e = ExceptionUtils.unwrapInvocationTargetException(e);
    ExceptionUtils.handleThrowable(e);
    throw new ServletException(sm.getString("filterChain.servlet"), e);
    } finally {
    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
    lastServicedRequest.set(null);
    lastServicedResponse.set(null);
    }
    }
    }
    }

使用范围不同

  • 过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
  • 而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。

触发时机不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
+-----------------------------------------------------------------------+
| |
| Tomcat |
| +-----------------------------------------------------------------+ |
| | Filter | |
| | +-----------------------------------------------------------+ | |
| | | Servlet | | |
| | | +------------------------------------------------------+ | | |
| | | | | | | |
| | | | Interceptor | | | |
| | | | | | | |
| | | | +----------------------------------------------+ | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | Controller | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | +----------------------------------------------+ | | | |
| | | | | | | |
| | | +------------------------------------------------------+ | | |
| | | | | |
| | +-----------------------------------------------------------+ | |
| | | |
| +-----------------------------------------------------------------+ |
| |
| |
| |
+-----------------------------------------------------------------------+
*/
  • 过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
  • 拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

拦截的请求范围不同

  • 执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

    1
    2
    3
    4
    5
    Filter 处理中
    Interceptor 前置
    Interceptor 处理中
    Interceptor 后置
    Filter 处理中
  • 过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。