Spring Boot 如何使用拦截器、过滤器、监听器?
阅读原文时间:2020年09月21日阅读:1

过滤器

过滤器的英文名称为 Filter, 是 Servlet 技术中最实用的技术。

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

过滤器的使用

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

  • init 方法:在容器中创建当前过滤器的时候自动调用

  • destory 方法:在容器中销毁当前过滤器的时候自动调用

  • doFilter 方法:过滤的具体操作

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

<dependency>&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.projectlombok</groupId>&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>lombok</artifactId></dependency><dependency>&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.springframework.boot</groupId>&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>spring-boot-starter-web</artifactId></dependency>

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

import&nbsp;lombok.extern.log4j.Log4j2;import&nbsp;org.springframework.stereotype.Component;import&nbsp;javax.servlet.*;import&nbsp;javax.servlet.http.HttpServletRequest;import&nbsp;javax.servlet.http.HttpServletResponse;import&nbsp;javax.servlet.http.HttpServletResponseWrapper;import&nbsp;java.io.IOException;@Log4j2public&nbsp;class&nbsp;MyFilter&nbsp;implements&nbsp;Filter&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;init(FilterConfig&nbsp;filterConfig)&nbsp;throws&nbsp;ServletException&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info("初始化过滤器");&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;doFilter(ServletRequest&nbsp;servletRequest,&nbsp;ServletResponse&nbsp;response,&nbsp;FilterChain&nbsp;filterChain)&nbsp;throws&nbsp;IOException,&nbsp;ServletException&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServletRequest&nbsp;request&nbsp;=&nbsp;(HttpServletRequest)servletRequest;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServletResponseWrapper&nbsp;wrapper&nbsp;=&nbsp;new&nbsp;HttpServletResponseWrapper((HttpServletResponse)&nbsp;response);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;requestUri&nbsp;=&nbsp;request.getRequestURI();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info("请求地址是:"+requestUri);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(requestUri.contains("/addSession")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;||&nbsp;requestUri.contains("/removeSession")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;||&nbsp;requestUri.contains("/online")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;||&nbsp;requestUri.contains("/favicon.ico"))&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filterChain.doFilter(servletRequest,&nbsp;response);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;else&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wrapper.sendRedirect("/online");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;destroy()&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//在服务关闭时销毁&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info("销毁过滤器");&nbsp;&nbsp;&nbsp;&nbsp;}}

拦截器

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

  • 登录认证:在一些应用中,可能会通过拦截器来验证用户的登录状态,如果没有登录或者登录失败,就会给用户一个友好的提示或者返回登录页面,当然大型项目中都不采用这种方式,都是调单点登录系统接口来验证用户。

  • 记录系统日志:我们在常见应用中,通常要记录用户的请求信息,比如请求 ip,方法执行时间等,通过这些记录可以监控系统的状况,以便于对系统进行信息监控、信息统计、计算 PV、性能调优等。

  • 通用处理:在应用程序中可能存在所有方法都要返回的信息,这是可以利用拦截器来实现,省去每个方法冗余重复的代码实现。

使用拦截器

我们需要实现 HandlerInterceptor 类,并且重写三个方法:

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

  • postHandler:在 Controoler 处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。

  • afterCompletion:在 DispatcherServlet 完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。

    import lombok.extern.log4j.Log4j2;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 javax.servlet.http.HttpSession;@Log4j2@Componentpublic class MyInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        log.info("【MyInterceptor】调用了:{}", request.getRequestURI());        request.setAttribute("requestTime", System.currentTimeMillis());        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response,                           Object handler, ModelAndView modelAndView) throws Exception {        if (!request.getRequestURI().contains("/online")) {            HttpSession session = request.getSession();            String sessionName = (String) session.getAttribute("name");            if ("haixiang".equals(sessionName)) {                log.info("【MyInterceptor】当前浏览器存在 session:{}",sessionName);            }        }    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,                                Object handler, Exception ex) throws Exception {        long duration = (System.currentTimeMillis() - (Long)request.getAttribute("requestTime"));        log.info("【MyInterceptor】[{}]调用耗时:{}ms",request.getRequestURI(), duration);    }}

监听器

监听器通常用于监听 Web 应用程序中对象的创建、销毁等动作的发送,同时对监听的情况作出相应的处理,最常用于统计网站的在线人数、访问量等。关注公众号Java技术栈回复boot可以获取更多系列 Spring Boot 教程。

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

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

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

  • ServletRequestListener:用来监听 Request 对象的属性操作。

监听器的使用

我们通过 HttpSessionListener来统计当前在线人数、ip等信息,为了避免并发问题我们使用原子int来计数。

ServletContext,是一个全局的储存信息的空间,它的生命周期与Servlet容器也就是服务器保持一致,服务器关闭才销毁。

request,一个用户可有多个;

session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。

因此我们这里用ServletContext来存储在线人数sessionCount最为合适。

我们下面来统计当前在线人数:

import&nbsp;lombok.extern.log4j.Log4j2;import&nbsp;javax.servlet.http.HttpSessionEvent;import&nbsp;javax.servlet.http.HttpSessionListener;import&nbsp;java.util.concurrent.atomic.AtomicInteger;@Log4j2public&nbsp;class&nbsp;MyHttpSessionListener&nbsp;implements&nbsp;HttpSessionListener&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;AtomicInteger&nbsp;userCount&nbsp;=&nbsp;new&nbsp;AtomicInteger(0);&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;synchronized&nbsp;void&nbsp;sessionCreated(HttpSessionEvent&nbsp;se)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;userCount.getAndIncrement();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;se.getSession().getServletContext().setAttribute("sessionCount",&nbsp;userCount.get());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info("【在线人数】人数增加为:{}",userCount.get());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//此处可以在ServletContext域对象中为访问量计数,然后传入过滤器的销毁方法&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//在销毁方法中调用数据库入库,因为过滤器生命周期与容器一致&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;synchronized&nbsp;void&nbsp;sessionDestroyed(HttpSessionEvent&nbsp;se)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;userCount.getAndDecrement();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;se.getSession().getServletContext().setAttribute("sessionCount",&nbsp;userCount.get());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info("【在线人数】人数减少为:{}",userCount.get());&nbsp;&nbsp;&nbsp;&nbsp;}}

实例化三器

import&nbsp;com.anqi.tool.sanqi.filter.MyFilter;import&nbsp;com.anqi.tool.sanqi.interceptor.MyInterceptor;import&nbsp;com.anqi.tool.sanqi.listener.MyHttpRequestListener;import&nbsp;com.anqi.tool.sanqi.listener.MyHttpSessionListener;import&nbsp;org.springframework.beans.factory.annotation.Autowired;import&nbsp;org.springframework.boot.web.servlet.FilterRegistrationBean;import&nbsp;org.springframework.boot.web.servlet.ServletListenerRegistrationBean;import&nbsp;org.springframework.context.annotation.Bean;import&nbsp;org.springframework.context.annotation.Configuration;import&nbsp;org.springframework.web.servlet.config.annotation.InterceptorRegistry;import&nbsp;org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic&nbsp;class&nbsp;WebConfig&nbsp;implements&nbsp;WebMvcConfigurer&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Autowired&nbsp;&nbsp;&nbsp;&nbsp;MyInterceptor&nbsp;myInterceptor;&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;addInterceptors(InterceptorRegistry&nbsp;registry)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;registry.addInterceptor(myInterceptor);&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;/**&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;注册过滤器&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;@Bean&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;FilterRegistrationBean&nbsp;filterRegistrationBean(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FilterRegistrationBean&nbsp;filterRegistration&nbsp;=&nbsp;new&nbsp;FilterRegistrationBean();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filterRegistration.setFilter(new&nbsp;MyFilter());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;filterRegistration.addUrlPatterns("/*");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;filterRegistration;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;/**&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;注册监听器&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;@Bean&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;ServletListenerRegistrationBean&nbsp;registrationBean(){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ServletListenerRegistrationBean&nbsp;registrationBean&nbsp;=&nbsp;new&nbsp;ServletListenerRegistrationBean();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;registrationBean.setListener(new&nbsp;MyHttpRequestListener());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;registrationBean.setListener(new&nbsp;MyHttpSessionListener());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;registrationBean;&nbsp;&nbsp;&nbsp;&nbsp;}}

测试

import&nbsp;com.anqi.tool.sanqi.listener.MyHttpSessionListener;import&nbsp;org.springframework.web.bind.annotation.GetMapping;import&nbsp;org.springframework.web.bind.annotation.RestController;import&nbsp;javax.servlet.http.HttpServletRequest;import&nbsp;javax.servlet.http.HttpSession;@RestControllerpublic&nbsp;class&nbsp;TestController&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@GetMapping("addSession")&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;addSession(HttpServletRequest&nbsp;request)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;request.getSession();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.setAttribute("name",&nbsp;"haixiang");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"当前在线人数"&nbsp;+&nbsp;session.getServletContext().getAttribute("sessionCount")&nbsp;+&nbsp;"人";&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@GetMapping("removeSession")&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;removeSession(HttpServletRequest&nbsp;request)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpSession&nbsp;session&nbsp;=&nbsp;request.getSession();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.invalidate();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"当前在线人数"&nbsp;+&nbsp;session.getServletContext().getAttribute("sessionCount")&nbsp;+&nbsp;"人";&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@GetMapping("online")&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;online()&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"当前在线人数"&nbsp;+&nbsp;MyHttpSessionListener.userCount.get()&nbsp;+&nbsp;"人";&nbsp;&nbsp;&nbsp;&nbsp;}}

以下是监听请求的监听器

import&nbsp;javax.servlet.ServletRequestEvent;import&nbsp;javax.servlet.ServletRequestListener;import&nbsp;javax.servlet.http.HttpServletRequest;public&nbsp;class&nbsp;MyHttpRequestListener&nbsp;implements&nbsp;ServletRequestListener&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;requestDestroyed(ServletRequestEvent&nbsp;sre)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("request&nbsp;监听器被销毁");&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;requestInitialized(ServletRequestEvent&nbsp;sre)&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpServletRequest&nbsp;req&nbsp;=&nbsp;(HttpServletRequest)&nbsp;sre.getServletRequest();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;requestURI&nbsp;=&nbsp;req.getRequestURI();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(requestURI+"--"+"被调用");&nbsp;&nbsp;&nbsp;&nbsp;}}

1.参考标准

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

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

2.实现方式

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

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

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://shimo.im/docs/rQRKDPx6dPXqvYPq