正在加载今日诗词....

SpringBoot使用拦截器、过滤器、监听器

PS:本文转载自海向博客

原文地址:[https://www.cnblogs.com/haixiang/p/12000685.html]

过滤器#

过滤器简介#

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

过滤器的使用#

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

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

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

Copy
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

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

Copy
import lombok.extern.log4j.Log4j2; import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;

@Log4j2
public class MyFilter implements Filter {

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(FilterConfig filterConfig)</span> <span class="hljs-keyword">throws</span> ServletException </span>{
    log.info(<span class="hljs-string">"初始化过滤器"</span>);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doFilter</span><span class="hljs-params">(ServletRequest servletRequest, ServletResponse response, FilterChain filterChain)</span> <span class="hljs-keyword">throws</span> IOException, ServletException </span>{
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponseWrapper wrapper = <span class="hljs-keyword">new</span> HttpServletResponseWrapper((HttpServletResponse) response);
    String requestUri = request.getRequestURI();
    log.info(<span class="hljs-string">"请求地址是:"</span>+requestUri);
    <span class="hljs-keyword">if</span> (requestUri.contains(<span class="hljs-string">"/addSession"</span>)
        || requestUri.contains(<span class="hljs-string">"/removeSession"</span>)
        || requestUri.contains(<span class="hljs-string">"/online"</span>)
        || requestUri.contains(<span class="hljs-string">"/favicon.ico"</span>)) {
        filterChain.doFilter(servletRequest, response);
    } <span class="hljs-keyword">else</span> {
        wrapper.sendRedirect(<span class="hljs-string">"/online"</span>);
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">destroy</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">//在服务关闭时销毁</span>
    log.info(<span class="hljs-string">"销毁过滤器"</span>);
}

}

拦截器#

拦截器介绍#

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

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

使用拦截器#

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

  • preHandle:在 Controoler 处理请求之前被调用,返回值是 boolean类型,如果是true就进行下一步操作;若返回false,则证明不符合拦截条件,在失败的时候不会包含任何响应,此时需要调用对应的response返回对应响应。
  • postHandler:在 Controoler 处理请求执行完成后、生成视图前执行,可以通过ModelAndView对视图进行处理,当然ModelAndView也可以设置为 null。
  • afterCompletion:在 DispatcherServlet 完全处理请求后被调用,通常用于记录消耗时间,也可以对一些资源进行处理。
Copy
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
@Component
public 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;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">postHandle</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response,
                       Object handler, ModelAndView modelAndView)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-keyword">if</span> (!request.getRequestURI().contains(<span class="hljs-string">"/online"</span>)) {
        HttpSession session = request.getSession();
        String sessionName = (String) session.getAttribute(<span class="hljs-string">"name"</span>);
        <span class="hljs-keyword">if</span> (<span class="hljs-string">"haixiang"</span>.equals(sessionName)) {
            log.info(<span class="hljs-string">"【MyInterceptor】当前浏览器存在 session:{}"</span>,sessionName);
        }
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterCompletion</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response,
                            Object handler, Exception ex)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-keyword">long</span> duration = (System.currentTimeMillis() - (Long)request.getAttribute(<span class="hljs-string">"requestTime"</span>));
    log.info(<span class="hljs-string">"【MyInterceptor】[{}]调用耗时:{}ms"</span>,request.getRequestURI(), duration);
}

}

监听器#

监听器简介#

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

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

  • ServletContextListener:用来监听 ServletContext 属性的操作,比如新增、修改、删除。
  • HttpSessionListener:用来监听 Web 应用种的 Session 对象,通常用于统计在线情况。
  • ServletRequestListener:用来监听 Request 对象的属性操作。

监听器的使用#

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

ServletContext,是一个全局的储存信息的空间,它的生命周期与Servlet容器也就是服务器保持一致,服务器关闭才销毁。request,一个用户可有多个;session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。因此我们这里用ServletContext来存储在线人数sessionCount最为合适。

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

Copy
import lombok.extern.log4j.Log4j2;

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.concurrent.atomic.AtomicInteger;

@Log4j2
public class MyHttpSessionListener implements HttpSessionListener {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> AtomicInteger userCount = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);

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

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">sessionDestroyed</span><span class="hljs-params">(HttpSessionEvent se)</span> </span>{
    userCount.getAndDecrement();
    se.getSession().getServletContext().setAttribute(<span class="hljs-string">"sessionCount"</span>, userCount.get());
    log.info(<span class="hljs-string">"【在线人数】人数减少为:{}"</span>,userCount.get());
}

}

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

实例化三器#

Copy
import com.anqi.tool.sanqi.filter.MyFilter; import com.anqi.tool.sanqi.interceptor.MyInterceptor; import com.anqi.tool.sanqi.listener.MyHttpRequestListener; import com.anqi.tool.sanqi.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;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
MyInterceptor myInterceptor;

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addInterceptors</span><span class="hljs-params">(InterceptorRegistry registry)</span> </span>{
    registry.addInterceptor(myInterceptor);
}

<span class="hljs-comment">/**
 * 注册过滤器
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> FilterRegistrationBean <span class="hljs-title">filterRegistrationBean</span><span class="hljs-params">()</span></span>{
    FilterRegistrationBean filterRegistration = <span class="hljs-keyword">new</span> FilterRegistrationBean();
    filterRegistration.setFilter(<span class="hljs-keyword">new</span> MyFilter());
    filterRegistration.addUrlPatterns(<span class="hljs-string">"/*"</span>);
    <span class="hljs-keyword">return</span> filterRegistration;
}

<span class="hljs-comment">/**
 * 注册监听器
 * <span class="hljs-doctag">@return</span>
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> ServletListenerRegistrationBean <span class="hljs-title">registrationBean</span><span class="hljs-params">()</span></span>{
    ServletListenerRegistrationBean registrationBean = <span class="hljs-keyword">new</span> ServletListenerRegistrationBean();
    registrationBean.setListener(<span class="hljs-keyword">new</span> MyHttpRequestListener());
    registrationBean.setListener(<span class="hljs-keyword">new</span> MyHttpSessionListener());
    <span class="hljs-keyword">return</span> registrationBean;
}

}

测试#

Copy
import com.anqi.tool.sanqi.listener.MyHttpSessionListener; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
public class TestController {

<span class="hljs-meta">@GetMapping("addSession")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">addSession</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
    HttpSession session = request.getSession();
    session.setAttribute(<span class="hljs-string">"name"</span>, <span class="hljs-string">"haixiang"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">"当前在线人数"</span> + session.getServletContext().getAttribute(<span class="hljs-string">"sessionCount"</span>) + <span class="hljs-string">"人"</span>;
}

<span class="hljs-meta">@GetMapping("removeSession")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">removeSession</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
    HttpSession session = request.getSession();
    session.invalidate();
    <span class="hljs-keyword">return</span> <span class="hljs-string">"当前在线人数"</span> + session.getServletContext().getAttribute(<span class="hljs-string">"sessionCount"</span>) + <span class="hljs-string">"人"</span>;
}

<span class="hljs-meta">@GetMapping("online")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">online</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"当前在线人数"</span> + MyHttpSessionListener.userCount.get() + <span class="hljs-string">"人"</span>;
}

}

以下是监听请求的监听器

Copy
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest;

public class MyHttpRequestListener implements ServletRequestListener {

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">requestDestroyed</span><span class="hljs-params">(ServletRequestEvent sre)</span> </span>{
    System.out.println(<span class="hljs-string">"request 监听器被销毁"</span>);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">requestInitialized</span><span class="hljs-params">(ServletRequestEvent sre)</span> </span>{
    HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
    String requestURI = req.getRequestURI();
    System.out.println(requestURI+<span class="hljs-string">"--"</span>+<span class="hljs-string">"被调用"</span>);
}

}

拦截器与过滤器的区别#

1.参考标准

  • 过滤器是 JavaEE 的标准,依赖于 Servlet 容器,生命周期也与容器一致,利用这一特性可以在销毁时释放资源或者数据入库。
  • 拦截器是SpringMVC中的内容,依赖于web框架,通常用于验证用户权限或者记录日志,但是这些功能也可以利用 AOP 来代替。

2.实现方式

  • 过滤器是基于回调函数实现,无法注入 ioc 容器中的 bean。
  • 拦截器是基于反射来实现,因此拦截器中可以注入 ioc 容器中的 bean,例如注入 Redis 的业务层来验证用户是否已经登录。
posted @ 2020-11-12 16:17  就永远夏天  阅读(127)  评论(0)    收藏  举报