1. 问题描述
首先编写一个Filter类并通过@Component放入spring容器中,通过实现jakarta.servlet中提供的Filter接口完成过滤器的创建,代码如下。
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter 前置.....");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("filter 后置.....");
}
}
为了将过滤器配置到目标路径下,通常有两种方法:
使用servlet提供的@WebFilter注解完成过滤路径的配置。
@Component
@WebFilter(urlPatterns = "/api/*",filterName = "myFilterName")
public class MyFilter implements Filter {
代码同上,省略
}
使用spring boot推荐的配置类FilterRegistrationBean完成过滤路径的配置。
笔者在使用@WebFilter注解进行过滤路径配置时发现,所有路径下的请求都被过滤了,不受@WebFilter注解中的参数控制,而使用spring推荐的配置类方法则不会出现这个问题。
2. 问题分析
@WebFilter、@WebServlet 和 @WebListener 等与Filter相关的注解均是在Servlet 容器中,spring容器中并没有这些注解,所以即使在Filter类中加上@WebFilter注解并配置好路径参数,spring工程中也读不到这个配置,spring会使用spring配置类的配置,如果spring配置类中没有对Filter进行配置,就会使用默认配置,即针对所以路径进行过滤,所以才会出现以上现象,使用@WebFilter配置类过滤路径,但Filter还是会对所有路径进行过滤。
3. 解决方案
3.1 spring配置类
最简单的解决方案就是不使用@WebFilter注解对过滤路径进行配置,而是使用spring推荐的配置类的方式进行配置。
@Configuration // 专门对 springMVC 底层做一些配置
public class MySpringMVCConfig implements WebMvcConfigurer{
@Autowired
private MyFilter myFilter;
@Bean
public FilterRegistrationBean getFilter1Registration() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(this.myFilter);
//设置过滤器名称和路径,在过滤器类写了的话,这里不用重复写
filterRegistrationBean.setName("filter");
filterRegistrationBean.addUrlPatterns("/api/*");
//设置过滤器执行顺序,数字越小,越早进行过滤,也可设置为负数
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
}
3.2 @WebFilter注解
如果非要使用@WebFilter注解的话:
第一步:需要把@WebFilter添加到容器中,因为spring容器中并没有@WebFilter的注解,这个注解在servlet容器中,故需要在启动类中添加@ServletComponentScan 注解启用 Servlet 容器扫描 @WebFilter
、@WebServlet
和 @WebListener
注解,将这些组件注册到 Servlet 容器中。
@SpringBootApplication
@ServletComponentScan
public class SpringmvcRestfulCrudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringmvcRestfulCrudApplication.class, args);
}
}
增加此注解后,@WebFilter就成功进入到Serlet容器中,并生效了,能够根据读取@WebFilter中的配置对配置内的路径进行过滤。但如果访问的是@WebFilter配置内的路径,程序会对此路径进行两次过滤,如果访问的路径不在@WebFilter配置内,则依然会被过滤一次。
这是因为在Filter类中添加了@Component注解,
如果同时使用@Component和@ServletComponentScan
这两个注解,Filter
类既会被 Spring 容器实例化,也会被 Servlet 容器实例化,导致被实例化两次。spring容器中实例化的Filter依然使用配置类的FilterRegistrationBean配置,如果没有则使用默认配置进行所有路径过滤。
所以就会出现在启动类增加@ServletComponentScan注解后,如果路径在@WebFilter配置内的访问会被过滤两次,一次是servlet容器实例化的Filter过滤的,另一次是spring容器实例化的Filter过滤。如果路径不在@WebFilter配置内依然会被过滤一次,这一次是spring容器中的Filter使用了默认配置进行的全路径过滤。
第二步:为了使用@WebFilter进行配置,不受spring容器实例化的Filter干扰,即将Filter类中的@Component注解删掉,Filter类不受spring容器控制,也不会进行实例化,这样就不会有两个实例化的Filter进行相互干扰了。
//@Component 不放到spring容器中管理
@WebFilter(urlPatterns = "/api/*",filterName = "myFilterName")
public class MyFilter implements Filter {
代码同上,省略
}
4 总结
综上所述,如果在spring boot工程中使用过滤器Filter,推荐在配置类中使用FilterRegistrationBean进行过滤路径配置,并在Filter类中添加@Component将Filter类加载到spring容器中。
如果要使用@WebFilter进行过滤路径的配置,则需要在启动类中添加
@ServletComponentScan注解启用Servlet容器扫描@WebFilter注解,并删除Filter类中的
@Component注解,防止spring实例化的Filter与
Servlet容器实例化的Filter冲突。
今后读者在使用过程中只要意识到自己过滤器Filter是由spring实例化的还是servlet实例化的,两个实例化的Filter所使用的配置方式不一样,要使用对应的配置方式。万万不可同时使用@Component注解和@ServletComponetScan注解。