SpringBoot 拦截器 过滤器 监听器

过滤器

过滤器的英文名称为 Filter,是 Servlet 技术中最实用的技术。
如同它的名字一样,过滤器是处于客户端和服务器资源文件之间的一道过滤网,过滤掉一些不符合要求的请求,通常用作 Session 校验,判断用户权限,如果不符合设定条件,则会被拦截到特殊的地址或者基于特殊的响应。

过滤器的使用

首先需要实现 Filter接口然后重写它的三个方法

  • init 方法:在容器中创建当前过滤器的时候自动调用
  • destory 方法:在容器中销毁当前过滤器的时候自动调用
  • doFilter 方法:过滤的具体操作

先引入 Maven 依赖,其中 lombok 是用来避免每个文件创建 Logger 来打印日志

  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>

首先实现接口,重写三个方法,对包含要求的四个请求予以放行,将其它请求拦截重定向至/online,只要在将MyFilter实例化后即可,在后面整合代码中一起给出。

  1. import lombok.extern.log4j.Log4j2;
  2. import org.springframework.stereotype.Component;
  3. import javax.servlet.*;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import javax.servlet.http.HttpServletResponseWrapper;
  7. import java.io.IOException;
  8. @Log4j2
  9. public class MyFilter implements Filter {
  10. @Override
  11. public void init(FilterConfig filterConfig) throws ServletException {
  12. log.info("初始化过滤器");
  13. }
  14. @Override
  15. public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
  16. HttpServletRequest request = (HttpServletRequest)servletRequest;
  17. HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response);
  18. String requestUri = request.getRequestURI();
  19. log.info("请求地址是:"+requestUri);
  20. if (requestUri.contains("/addSession")
  21. || requestUri.contains("/removeSession")
  22. || requestUri.contains("/online")
  23. || requestUri.contains("/favicon.ico")) {
  24. filterChain.doFilter(servletRequest, response);
  25. } else {
  26. wrapper.sendRedirect("/online");
  27. }
  28. }
  29. @Override
  30. public void destroy() {
  31. //在服务关闭时销毁
  32. log.info("销毁过滤器");
  33. }
  34. }

有了过滤器类以后,以前的web项目可以在web.xml中进行配置,但是SpringBoot项目并没有web.xml这个文件,那怎么配置?在SpringBoot中,需要FilterRegistrationBean来完成配置。其实现过程如下:

  1. @Configuration
  2. public class FilterConfig {
  3. @Bean
  4. public FilterRegistrationBean registFilter() {
  5. FilterRegistrationBean registration = new FilterRegistrationBean();
  6. registration.setFilter(new LogCostFilter());
  7. registration.addUrlPatterns("/*");
  8. registration.setName("MyFilter");
  9. registration.setOrder(1);
  10. return registration;
  11. }
  12. }

基于注解的形式实现过滤器

直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是SpringBoot提供的。除了这个注解以外,还需在配置类中加另外一个注解:@ServletComponetScan,指定扫描的包。

  1. @SpringBootApplication
  2. @MapperScan("com.fcant.blog.dao")
  3. @ServletComponentScan("com.fcant.blog.filters")
  4. public class Application {
  5. public static void main(String[] args) throws Exception {
  6. SpringApplication.run(Application.class, args);
  7. }
  8. }

首先需要写一个方法继承Filter类,写如下两个自己的Filter类,首先是FirstFilter类,其中@Order里边的数字越小代表越先被该Filter过滤,@WebFilter代表这是个Filter类并把这个类注入到容器中:

  1. import java.io.IOException;
  2. import javax.servlet.Filter;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.FilterConfig;
  5. import javax.servlet.ServletException;
  6. import javax.servlet.ServletRequest;
  7. import javax.servlet.ServletResponse;
  8. import javax.servlet.annotation.WebFilter;
  9. import org.springframework.core.annotation.Order;
  10. @Order(1)
  11. @WebFilter(filterName="firstFilter", urlPatterns="/*")
  12. public class FirstFilter implements Filter {
  13. @Override
  14. public void init(FilterConfig filterConfig) throws ServletException {
  15. }
  16. @Override
  17. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  18. throws IOException, ServletException {
  19. System.out.println("first filter 1");
  20. chain.doFilter(request, response);
  21. System.out.println("first filter 2");
  22. }
  23. @Override
  24. public void destroy() {
  25. }
  26. }

拦截器

Java中的拦截器是动态拦截 action 调用的对象,然后提供了可以在 action 执行前后增加一些操作,也可以在 action 执行前停止操作,功能与过滤器类似,但是标准和实现方式不同。

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

    使用拦截器

    需要实现 HandlerInterceptor 接口,并且重写三个方法:

  • preHandle:在 Controoler 处理请求之前被调用,返回值是 boolean类型,如果是true就进行下一步操作;若返回false,则证明不符合拦截条件,在失败的时候不会包含任何响应,此时需要调用对应的response返回对应响应。

  • postHandler:在 Controoler 处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。
  • afterCompletion:在 DispatcherServlet 完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。

    1. import lombok.extern.log4j.Log4j2;
    2. import org.springframework.stereotype.Component;
    3. import org.springframework.web.servlet.HandlerInterceptor;
    4. import org.springframework.web.servlet.ModelAndView;
    5. import javax.servlet.http.HttpServletRequest;
    6. import javax.servlet.http.HttpServletResponse;
    7. import javax.servlet.http.HttpSession;
    8. @Log4j2
    9. @Component
    10. public class MyInterceptor implements HandlerInterceptor {
    11. @Override
    12. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    13. log.info("【MyInterceptor】调用了:{}", request.getRequestURI());
    14. request.setAttribute("requestTime", System.currentTimeMillis());
    15. return true;
    16. }
    17. @Override
    18. public void postHandle(HttpServletRequest request, HttpServletResponse response,
    19. Object handler, ModelAndView modelAndView) throws Exception {
    20. if (!request.getRequestURI().contains("/online")) {
    21. HttpSession session = request.getSession();
    22. String sessionName = (String) session.getAttribute("name");
    23. if ("haixiang".equals(sessionName)) {
    24. log.info("【MyInterceptor】当前浏览器存在 session:{}",sessionName);
    25. }
    26. }
    27. }
    28. @Override
    29. public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
    30. Object handler, Exception ex) throws Exception {
    31. long duration = (System.currentTimeMillis() - (Long)request.getAttribute("requestTime"));
    32. log.info("【MyInterceptor】[{}]调用耗时:{}ms",request.getRequestURI(), duration);
    33. }
    34. }

    监听器

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

  • ServletContextListener:用来监听 ServletContext 属性的操作,比如新增、修改、删除。

  • HttpSessionListener:用来监听 Web 应用种的 Session 对象,通常用于统计在线情况。
  • ServletRequestListener:用来监听 Request 对象的属性操作。

    监听器的使用

    通过 HttpSessionListener来统计当前在线人数、ip等信息,为了避免并发问题使用原子int来计数。
    ServletContext,是一个全局的储存信息的空间,它的生命周期与Servlet容器也就是服务器保持一致,服务器关闭才销毁。
    request,一个用户可有多个;
    session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。
    因们这里用ServletContext来存储在线人数sessionCount最为合适。
    来统计当前在线人数:

    1. import lombok.extern.log4j.Log4j2;
    2. import javax.servlet.http.HttpSessionEvent;
    3. import javax.servlet.http.HttpSessionListener;
    4. import java.util.concurrent.atomic.AtomicInteger;
    5. @Log4j2
    6. public class MyHttpSessionListener implements HttpSessionListener {
    7. public static AtomicInteger userCount = new AtomicInteger(0);
    8. @Override
    9. public synchronized void sessionCreated(HttpSessionEvent se) {
    10. userCount.getAndIncrement();
    11. se.getSession().getServletContext().setAttribute("sessionCount", userCount.get());
    12. log.info("【在线人数】人数增加为:{}",userCount.get());
    13. //此处可以在ServletContext域对象中为访问量计数,然后传入过滤器的销毁方法
    14. //在销毁方法中调用数据库入库,因为过滤器生命周期与容器一致
    15. }
    16. @Override
    17. public synchronized void sessionDestroyed(HttpSessionEvent se) {
    18. userCount.getAndDecrement();
    19. se.getSession().getServletContext().setAttribute("sessionCount", userCount.get());
    20. log.info("【在线人数】人数减少为:{}",userCount.get());
    21. }
    22. }

    过滤器、拦截器、监听器注册

    实例化三器

    1. import com.anqi.tool.sanqi.filter.MyFilter;
    2. import com.anqi.tool.sanqi.interceptor.MyInterceptor;
    3. import com.anqi.tool.sanqi.listener.MyHttpRequestListener;
    4. import com.anqi.tool.sanqi.listener.MyHttpSessionListener;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.boot.web.servlet.FilterRegistrationBean;
    7. import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    8. import org.springframework.context.annotation.Bean;
    9. import org.springframework.context.annotation.Configuration;
    10. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    11. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    12. @Configuration
    13. public class WebConfig implements WebMvcConfigurer {
    14. @Autowired
    15. MyInterceptor myInterceptor;
    16. @Override
    17. public void addInterceptors(InterceptorRegistry registry) {
    18. registry.addInterceptor(myInterceptor);
    19. }
    20. /**
    21. * 注册过滤器
    22. * @return
    23. */
    24. @Bean
    25. public FilterRegistrationBean filterRegistrationBean(){
    26. FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
    27. filterRegistration.setFilter(new MyFilter());
    28. filterRegistration.addUrlPatterns("/*");
    29. return filterRegistration;
    30. }
    31. /**
    32. * 注册监听器
    33. * @return
    34. */
    35. @Bean
    36. public ServletListenerRegistrationBean registrationBean(){
    37. ServletListenerRegistrationBean registrationBean = new ServletListenerRegistrationBean();
    38. registrationBean.setListener(new MyHttpRequestListener());
    39. registrationBean.setListener(new MyHttpSessionListener());
    40. return registrationBean;
    41. }
    42. }

    测试

    1. import com.anqi.tool.sanqi.listener.MyHttpSessionListener;
    2. import org.springframework.web.bind.annotation.GetMapping;
    3. import org.springframework.web.bind.annotation.RestController;
    4. import javax.servlet.http.HttpServletRequest;
    5. import javax.servlet.http.HttpSession;
    6. @RestController
    7. public class TestController {
    8. @GetMapping("addSession")
    9. public String addSession(HttpServletRequest request) {
    10. HttpSession session = request.getSession();
    11. session.setAttribute("name", "haixiang");
    12. return "当前在线人数" + session.getServletContext().getAttribute("sessionCount") + "人";
    13. }
    14. @GetMapping("removeSession")
    15. public String removeSession(HttpServletRequest request) {
    16. HttpSession session = request.getSession();
    17. session.invalidate();
    18. return "当前在线人数" + session.getServletContext().getAttribute("sessionCount") + "人";
    19. }
    20. @GetMapping("online")
    21. public String online() {
    22. return "当前在线人数" + MyHttpSessionListener.userCount.get() + "人";
    23. }
    24. }

    以下是监听请求的监听器

    1. import javax.servlet.ServletRequestEvent;
    2. import javax.servlet.ServletRequestListener;
    3. import javax.servlet.http.HttpServletRequest;
    4. public class MyHttpRequestListener implements ServletRequestListener {
    5. @Override
    6. public void requestDestroyed(ServletRequestEvent sre) {
    7. System.out.println("request 监听器被销毁");
    8. }
    9. @Override
    10. public void requestInitialized(ServletRequestEvent sre) {
    11. HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
    12. String requestURI = req.getRequestURI();
    13. System.out.println(requestURI+"--"+"被调用");
    14. }
    15. }

    拦截器与过滤器的区别

    1.参考标准

  • 过滤器是 JavaEE 的标准,依赖于 Servlet 容器,生命周期也与容器一致,利用这一特性可以在销毁时释放资源或者数据入库。

  • 拦截器是SpringMVC中的内容,依赖于web框架,通常用于验证用户权限或者记录日志,但是这些功能也可以利用 AOP 来代替。

    2.实现方式

  • 过滤器是基于回调函数实现,无法注入 ioc 容器中的 bean。

  • 拦截器是基于反射来实现,因此拦截器中可以注入 ioc 容器中的 bean,例如注入 Redis 的业务层来验证用户是否已经登录。