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思想的一种体现,都可以进行权限校验,日志记录等工作。
- 对于没有使用Spring的框架肯定是使用Filter
- 对于Spring项目可以根据Filter和Interceptor的执行顺序来灵活使用
- 大量的请求块信息处理使用filter, 特别的内部逻辑处理所使用aspect
- filter的作用范围中可以包含aspect和interceptor
- 在拦截器中可以注入Spring中的Bean对象
Interceptor中如何使用SpringBean?
在拦截器中可以直接通过@Autowired注入Spring中的Bean对象,但是一定要注意一点:
注册拦截器时,必须通过new的形式(包括上面用到的通过方法的返回值new+@Bean注解放入容器)创建拦截器。不能在拦截器上加入@Component注解在WebMvcConfigurer中配置。
Interceptor可以使用全局异常处理吗?
当然是可以的,也可以在拦截器中抛出自定义异常交由全局异常处理处理,这样就可以返回和Controller方法相同的返回值类型啦~