filter依赖于servlet,而interceptor是spring中有封装的,依赖于springmvc
过滤器
过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
- init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
- doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。
- destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次。
@Component
@WebFilter("/myservlet1")//过滤路径
public class MyFilter 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("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("Filter 后置");
}
}
优先级:可以使用@Order()注解,值越小级别越高越先执行。
● 如果为注解的话,是按照类全名称的字符串顺序决定作用顺序
● 如果web.xml,按照 filter-mapping注册顺序,从上往下
● web.xml配置高于注解方式
拦截器
拦截器: 本身会在请求进入Controller控制层前后做拦截处理.
常见使用场景:
- 接口重复提交校验 放置用户重复提交相同数据.
- 权限检查 当前用户是否登录,是否有权限访问数据.
- 日志记录 记录请求信息,输出成日志文件.
- 性能监控 慢日志.
使用实操
定义多个拦截器可以实现WebMvcConfigurer,重写添加拦截的方法
@Configuration
public class MVCConfig implements WebMvcConfigurer{
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(new LoginInterceptor())
// .addPathPatterns("/**") //所有请求都被拦截包括静态资源(如下实现静态资源放行)
// .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
// token刷新的拦截器,拦截所有请求
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。
实现HandlerInterceptor,定义拦截器
拦截器加载的时间点在springcontext之前,所以需要使用构造器传入spring容器中的bean;或者将自定义的拦截器注入容器+在这个类下注入需要使用的依赖
public class RefreshTokenInterceptor implements HandlerInterceptor {
//由于这个类是自定义的,非spring注解构建,所以不能使用spring依赖注入,
//可以使用构造器注入,也可以在拦截器类加上注解@component等
StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
重写三个方法...
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request before 拦截");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("post 拦截");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("after 拦截");
}
}
StringRedisTemplate是容器spring-data-redis包下的类
多个拦截器执行顺序
除重写的preHandle方法是先定义先执行外,其它两个方法都是后定义先执行
过滤器与拦截器的区别
1> 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
2> 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3> 拦截器只能对action请求起作用(拦截器只会对Controller中请求或访问static目录下的资源请求起作用。),而过滤器则可以对几乎所有的请求起作用。
4> 拦截器可以访问action上下文、栈值里的对象,而过滤器不能访问。
5> 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。(注:这里的调用一次,是对于构造函数而言。而doFilter会对匹配的请求做持续的处理。)
6> 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,所以我们可以在拦截器里注入一个service,可以调用业务逻辑。
过滤器、拦截器和IOC容器执行顺序
在Spring Boot中,过滤器、拦截器和IOC容器的执行顺序如下:
- 过滤器(Filter): 过滤器是在Servlet容器中的第一层,当请求进入Servlet容器时,会首先经过过滤器。过滤器的主要作用是对请求进行预处理,如身份验证、字符编码等。
- IOC容器: Spring Boot的IOC容器负责管理和维护所有的Bean对象,将根据配置文件或注解创建和初始化Bean对象,并且自动进行依赖注入。IOC容器会在应用程序启动时创建,根据配置的执行顺序初始化Bean对象。
- 拦截器(Interceptor): 拦截器在处理请求的过程中,对请求和响应进行拦截和处理。拦截器会在IOC容器创建并初始化好的Bean对象中进行设置,并且可以在请求前后进行一些特定的处理逻辑。
在Spring中,Filter是在Servlet容器中定义的,它在请求进入Servlet容器后,但在进入Spring的IOC容器之前执行。Interceptor则是在Spring的MVC框架中定义的,它在请求进入Spring的IOC容器之后执行。
filter之前调用ioc容器的bean
如果需要在Filter中调用IOC容器中的bean来查询数据库,可以通过手动获取IOC容器的方式来实现。以下是一个简化的示例代码:
public class MyFilter implements Filter {
private ApplicationContext context;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 获取ApplicationContext
context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// 在过滤器中调用IOC容器中的bean查询数据库
MyService myService = context.getBean(MyService.class);
myService.queryDatabase();
// 继续执行过滤器链
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
// 清理资源
}
}
在上述示例代码中,通过使用WebApplicationContextUtils
类的getWebApplicationContext
方法获取Root WebApplicationContext,然后通过getBean
方法获取需要调用的bean对象。注意,在使用Filter中调用IOC容器的bean时,需要确保IOC容器已经初始化完毕。
需要注意的是,这种方式破坏了Filter与IOC容器的解耦,一般不推荐在Filter中直接调用IOC容器中的bean。更好的做法是将查询数据库的逻辑放到Service层,然后在Filter中调用Service层的方法,遵循单一职责原则,提高代码的可维护性和可测试性。