SpringBoot中Interceptor和Filter的使用

SpringBoot中Interceptor和Filter的使用

如何使用拦截器和Filter

FIlter:过滤器,它是Servlet中的一个概念,主要的作用是对数据进行过滤、校验、记录日志,权限验证等操作。

使用Filter

创建类,实现javax.servlet.Filter接口。

package cn.rayfoo.common.util.FileterController;

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

/**
 * 在SpringBoot中通过注解注册的方式简单的使用Filter
 * @author rayfoo
 */
@WebFilter(urlPatterns = "/*", filterName = "myfilter")
public class FileterController implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter初始化中");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("开始进行过滤处理");
        //调用该方法后,表示过滤器经过原来的url请求处理方法
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("处理后的操作");
    }

    @Override
    public void destroy() {
        System.out.println("Filter销毁中");
    }
}

上述代码中,重写了Filter的三个方法,分别是:

  • init:在此Filter被创建时执行
  • doFilter:处理Filter的真正业务逻辑,可以在这个方法中对请求进行放行,在放行前后都可以执行代码, 也可以在此方法中进行重定向和请求转发,但是一旦使用了请求转发、重定向,抛出异常,出现异常,被拦截的路径对应的业务方法就不会被执行。
  • destory:在此FIlter被销毁时执行

SpringBoot中使用Filter

1、在Filter上加入@WebFilter(urlPatterns = "/path 也可以是*", filterName = "filterName")注解,配置urlPatterns和filterName

2、在启动类上加入@ServletComponentScan注解

在SpringBoot中使用Interceptor

1、创建类,实现HandlerInterceptor接口

2、Interceptor和Filter有所不同,HandlerInterceptor中有三个方法,由于JDK8之后支持了default关键字,其内的方

法都是使用default修饰的,不会提示我们手动重写,需要点击进入源码找到并复制到上面创建的类中,修改defalut为public。

3、在拦截器上加上@Component注解

package cn.rayfoo.common.interceptor;

import cn.rayfoo.common.exception.MyException;
import cn.rayfoo.common.response.HttpStatus;
import cn.rayfoo.common.util.net.ClientUtil;
import cn.rayfoo.common.util.redis.RedisUtil;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * @date 2020/8/6 11:49
 * @description 登录次数校验拦截器
 */
@NoArgsConstructor
@Getter
@Slf4j
public class AccessInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 需要拦截的URL
     */
    private String InterceptorUrl;

    /**
     * 规定的时间范围
     */
    private Long timeBound;

    /**
     * 在规定时间按内的访问量
     */
    private Integer requestNum;


    /**
     * @param InterceptorUrl 要拦截的URL
     * @param timeBound      规定的时间范围
     * @param requestNum     时间范围内的请求次数超过多少时禁止访问
     */
    public AccessInterceptor(String InterceptorUrl, Long timeBound, Integer requestNum) {
        this.InterceptorUrl = InterceptorUrl;
        this.timeBound = timeBound;
        this.requestNum = requestNum;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断该ip地址五分钟内注册了多少次 如果超过五次,将其ip列入黑名单十分钟。 每注册一次给此id加1
        String ipAddr = ClientUtil.getIpAddr(request) + InterceptorUrl;
        //判断ip地址是否存在
        if (redisUtil.hasKey(ipAddr)) {
            //获取五分钟内获取验证码的次数
            Integer count = (Integer) redisUtil.get(ipAddr);
            //如果在x分钟内获取了超过x次
            if (count > requestNum) {
                //获取过期时间
                long expire = redisUtil.getExpire(ipAddr);
                log.error(ClientUtil.getIpAddr(request) + "对" + InterceptorUrl + "操作频繁,此接口已将其暂时列入黑名单");
                //告知用户限制时间还有多久
                throw MyException.builder().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).msg("您的操作过于频繁,请" + expire + "秒后重试!").build();
            }
            //不到五次就累加
            redisUtil.incr(ipAddr, 1L);
        } else {
            //不存在的话 创建
            redisUtil.set(ipAddr, 1L);
            //过期时间设置为time秒
            redisUtil.expire(ipAddr, timeBound);
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }


}

在Interceptor中有三个方法,

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception 

其中preHandle是在业务方法执行之前执行,返回true表示放行请求,返回false表示不执行拦截的方法, 在此方法中同样可以进行重定向、转发(此时都会返回false),异常抛出等操作,出现此三种情况,都不会执行业务方法。

postHandle是在业务方法执行之后,但是在视图渲染之前执行,可以进行视图的一些操作,其参数中提供了一个modelAndView可以修改视图的路径和渲染的值。

afterCompletion是在视图渲染完成后执行的,可以进行关闭资源、日志记录等操作。

注册Interceptor

在SpringBoot2中,建议使用的注册拦截器的方法有如下两种:

  • 实现WebMvcConfigurer接口
  • 继承WebMvcConfigurerAdapter类(此类也是实现了WebMvcConfigurer

下面介绍一下实现WebMvcConfigurer方法注册拦截器

package cn.rayfoo.common.config;

import cn.rayfoo.common.interceptor.AccessInterceptor;
import cn.rayfoo.modules.base.interceptor.SMSValidateInterceptor;
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;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * @date 2020/8/6 9:43
 * @description 拦截器配置类
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 由于使用了其他依赖 将自定义的拦截器作为Bean写入配置
     * @return
     */
    @Bean
    public SMSValidateInterceptor getSMSValidateInterceptor(){
        return new SMSValidateInterceptor();
    }

    /**
     * 短信接口的拦截器
     * @return
     */
    @Bean
    public AccessInterceptor getCodeAccessInterceptor(){
        return new AccessInterceptor("/user/code",300L,5);
    }

    /**
     * 注册拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //注册短信验证码接口的请求次数拦截器
        AccessInterceptor codeAccessInterceptor = getCodeAccessInterceptor();
        registry.addInterceptor(codeAccessInterceptor)
                .addPathPatterns(codeAccessInterceptor.getInterceptorUrl());

        //注册手机号校验拦截器
        registry.addInterceptor(getSMSValidateInterceptor())
                .addPathPatterns("/user/code");

    }

}

在此类中,可以使用@Bean来创建多个拦截器对象,使用addInterceptors进行注册。注册时,需要提供拦截的路径、不拦截的路径。均为可选参数。

执行顺序

Interceptor和Filter的执行顺序是不同的,下面的图很清晰的描述了他们的执行顺序。

图2来自博客

拦截器链的执行顺序

当拦截器有2个或者两个以上的时候,他们的顺序如何指定,又如何指定拦截器的顺序呢?

在拦截器注册时,注册的顺序决定着他们执行的顺序。先注册的拦截器会先执行。具体的执行链可以参考下图:

什么时候使用Filter?什么时候使用Interceptor

拦截器和Filter都是对AOP思想的一种体现,都可以进行权限校验,日志记录等工作。

  1. 对于没有使用Spring的框架肯定是使用Filter
  2. 对于Spring项目可以根据Filter和Interceptor的执行顺序来灵活使用
  3. 大量的请求块信息处理使用filter, 特别的内部逻辑处理所使用aspect
  4. filter的作用范围中可以包含aspect和interceptor
  5. 在拦截器中可以注入Spring中的Bean对象

Interceptor中如何使用SpringBean?

在拦截器中可以直接通过@Autowired注入Spring中的Bean对象,但是一定要注意一点:

注册拦截器时,必须通过new的形式(包括上面用到的通过方法的返回值new+@Bean注解放入容器)创建拦截器。不能在拦截器上加入@Component注解在WebMvcConfigurer中配置。

Interceptor可以使用全局异常处理吗?

当然是可以的,也可以在拦截器中抛出自定义异常交由全局异常处理处理,这样就可以返回和Controller方法相同的返回值类型啦~

posted @ 2020-07-17 16:00  张瑞丰  阅读(1441)  评论(0编辑  收藏  举报