在springBoot项目中使用过滤器有两种方式
方式1:通过@WebFilter注解+@ServletComponentScan注解开启servlet组件扫描,过滤器是依赖于Servlet的,不依赖于Spring
方式2:自定义过滤器并通过FilterRegistrationBean实例注册,可以同时注册多个并且设置优先级
自定义过滤器,使用@WebFilter注解,其中:urlPatterns指定拦截过滤的URI(多个URI之间用逗号分隔),filterName指定过滤器名字;
@Slf4j
@WebFilter(urlPatterns = {"/user/*"}, filterName = "authFilter")
public class AuthFilter implements Filter {
private static final String AUTH_TOKEN = "token";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
final String token = req.getParameter(AUTH_TOKEN);
final AuthTokenService tokenService = ApplicationContextUtils.getBean(AuthTokenService.class);
final AuthResult result = (AuthResult) tokenService.checkToken(token);
if(result.getCode() == 1 ){
log.info("user auth token filter passed");
req.setAttribute("user_info", result.getData());
filterChain.doFilter(servletRequest, servletResponse);
}else{
throw new AuthTokenException(result.getMessage());
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
此外: 使用@WebFilter注解时,必须在Springboot启动类上加@ServeltComponentScan注解,用来扫描servlet组件(包括:@WebFilter和@WebListener),否则该过滤器无法注册!
缺点:这种方式实现的过滤器无法控制多个过滤器的执行顺序(默认执行的顺序是按照类名字母的排序),使用注解@Order或者Ordered接口的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响,当然也无法定义Servlet组件的加载和执行顺序
但是可以通过,把Filter注册成Spring中的Bean,并通过@Order注解控制Bean的执行顺序,代码如下
自定义AuthFilter的bean和TestFilter,具体代码如下
@Slf4j
@Component
@WebFilter(urlPatterns = "/test")
@Order(900)
public class AuthFilter implements Filter {
public AuthFilter() {
log.info("init auth");
}
private static final String AUTH_TOKEN = "token";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("auth filter");
filterChain.doFilter(servletRequest, servletResponse);
}
}
@Slf4j
@Component
@Order(500)
public class TestFilter implements Filter {
public TestFilter() {
log.info("init test");
}
private static final String AUTH_TOKEN = "token";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("test filter");
filterChain.doFilter(servletRequest, servletResponse);
}
}
启动日志如下:
2020-01-10 20:13:12.868 INFO 21988 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1354 ms
2020-01-10 20:13:12.889 INFO 21988 --- [ main] com.geeron.authdemo.Filter.AuthFilter : init auth
2020-01-10 20:13:12.892 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Added existing Servlet initializer bean 'dispatcherServletRegistration'; order=2147483647, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class]
2020-01-10 20:13:12.893 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Added existing Filter initializer bean 'com.geeron.authdemo.Filter.AuthFilter'; order=2147483647, resource=null
2020-01-10 20:13:12.895 INFO 21988 --- [ main] com.geeron.authdemo.Filter.AuthFilter : init auth
2020-01-10 20:13:12.895 INFO 21988 --- [ main] com.geeron.authdemo.Filter.TestFilter : init test
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'characterEncodingFilter'; order=-2147483648, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration.class]
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'hiddenHttpMethodFilter'; order=-10000, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'formContentFilter'; order=-9900, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.class]
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'requestContextFilter'; order=-105, resource=class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'testFilter'; order=500, resource=file [E:\workspace\auth-demo\target\classes\com\geeron\authdemo\Filter\TestFilter.class]
2020-01-10 20:13:12.905 TRACE 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Created Filter initializer for bean 'authFilter'; order=900, resource=file [E:\workspace\auth-demo\target\classes\com\geeron\authdemo\Filter\AuthFilter.class]
2020-01-10 20:13:12.910 DEBUG 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: com.geeron.authdemo.Filter.AuthFilter urls=[/auth], characterEncodingFilter urls=[/*], hiddenHttpMethodFilter urls=[/*], formContentFilter urls=[/*], requestContextFilter urls=[/*], testFilter urls=[/*], authFilter urls=[/*]
2020-01-10 20:13:12.910 DEBUG 21988 --- [ main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
2020-01-10 20:13:12.931 DEBUG 21988 --- [ main] o.s.b.w.s.f.OrderedRequestContextFilter : Filter 'requestContextFilter' configured for use
2020-01-10 20:13:12.932 DEBUG 21988 --- [ main] .s.b.w.s.f.OrderedHiddenHttpMethodFilter : Filter 'hiddenHttpMethodFilter' configured for use
2020-01-10 20:13:12.932 DEBUG 21988 --- [ main] s.b.w.s.f.OrderedCharacterEncodingFilter : Filter 'characterEncodingFilter' configured for use
2020-01-10 20:13:12.932 DEBUG 21988 --- [ main] o.s.b.w.s.f.OrderedFormContentFilter : Filter 'formContentFilter' configured for use
inint ssss
2020-01-10 20:13:13.128 INFO 21988 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-01-10 20:13:13.270 TRACE 21988 --- [ main] ConfigServletWebServerApplicationContext : No 'lifecycleProcessor' bean, using [DefaultLifecycleProcessor]
2020-01-10 20:13:13.300 INFO 21988 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path ''
2020-01-10 20:13:13.303 INFO 21988 --- [ main] com.geeron.authdemo.AuthDemoApplication : Started AuthDemoApplication in 2.289 seconds (JVM running for 3.268)
从日志中可以看出AuthFilter初始化了2次,且第二次的路径是/*,且顺序不是按照@Order注解设置的顺序(这里有没有@Order注解,都是先初始化AuthFilter,在初始化TestFilter),此外可以看到过滤器的的初始化如下:
Mapping filters: com.geeron.authdemo.Filter.AuthFilter urls=[/auth],.... testFilter urls=[/*], authFilter urls=[/*]
执行http://localhost:8888/hello测试过滤器的执行:都是先执行TestFilter,在执行AuthFilter,如果没有@Order注解则相反
2020-01-10 20:19:04.185 INFO 21988 --- [nio-8888-exec-1] com.geeron.authdemo.Filter.TestFilter : test filter
2020-01-10 20:19:07.064 INFO 21988 --- [nio-8888-exec-1] com.geeron.authdemo.Filter.AuthFilter : auth filter
由以上可以的得出以下几个结论
主要是利用FilterRegistrationBean去注册过滤器,并可以设置相关的属性和执行顺序,代码简单,一看即懂:
@Slf4j
public class AuthFilter implements Filter {
private static final String AUTH_TOKEN = "token";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
final String token = req.getParameter(AUTH_TOKEN);
final AuthTokenService tokenService = ApplicationContextUtils.getBean(AuthTokenService.class);
final AuthResult result = (AuthResult) tokenService.checkToken(token);
if(result.getCode() == 1 ){
log.info("user auth token filter passed");
req.setAttribute("user_info", result.getData());
filterChain.doFilter(servletRequest, servletResponse);
}else{
throw new AuthTokenException(result.getMessage());
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean RegistAuth(){
//通过FilterRegistrationBean实例设置优先级可以生效
//通过@WebFilter无效
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new AuthFilter());//注册自定义过滤器
bean.setName("AuthFilter");//过滤器名称
bean.addUrlPatterns("/auth/*");//过滤所有路径
bean.setOrder(1);//优先级,最顶级
return bean;
}
@Bean
public FilterRegistrationBean RegistTest2(){
//通过FilterRegistrationBean实例设置优先级可以生效
//通过@WebFilter无效
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new TestFilter());//注册自定义过滤器
bean.setName("flilter2");//过滤器名称
bean.addUrlPatterns("/*");//过滤所有路径
bean.setOrder(6);//优先级,越低越优先
return bean;
}
}
拓展:除了实现Filter,在Spring的项目中还可以实现OncePerRequestFilter,那么他们有什么区别呢?
可以从名字就可以看出OncePerRequestFilter是执行一次请求的过滤器,确保在一次请求只通过一次filter,那言外之意就是Filter可能会执行多次?在什么情况下Filter会执行多次呢?事实上OncePerRequestFilter是为了避免不同servlet 容器的不同行为而出现的,为了兼容不同的web container,特意而为之(jsr168),也就是说并不是所有的container都像我们期望的只过滤一次,servlet版本不同,表现也不同,如下
/**
* Filter base class that guarantees to be just executed once per request,
* on any servlet container. It provides a {@link #doFilterInternal}
* method with HttpServletRequest and HttpServletResponse arguments.
*
* The {@link #getAlreadyFilteredAttributeName} method determines how
* to identify that a request is already filtered. The default implementation
* is based on the configured name of the concrete filter instance.
*
* @author Juergen Hoeller
* @since 06.12.2003
*/
此外不同的servlet版本也有不同的差异,servlet2.3与servlet2.4也有一定差异 ,如下:
在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。 在servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。
因此,为了兼容各种不同的运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择。
/**
* 过滤器基类,旨在确保每个请求调度在任何servlet容器上执行一次执行。
* 它提供了一个带有HttpServletRequest和HttpServletResponse参数的{@link #doFilterInternal}方法。
*/
public abstract class OncePerRequestFilter extends GenericFilterBean {
//附加到“已过滤”请求属性的过滤器名称的后缀。
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
//这个doFilter实现存储“已经过滤”的请求属性,如果该属性已经存在,则不进行再次过滤。
//@see #getAlreadyFilteredAttributeName
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// 继续而不调用此过滤器...
filterChain.doFilter(request, response);
}
else {
// 调用这个过滤器…
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// 删除此请求的“已过滤”请求属性。
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}