SpringBoot中使用过滤器

定义过滤器

在springBoot项目中使用过滤器有两种方式

方式1:通过@WebFilter注解+@ServletComponentScan注解开启servlet组件扫描,过滤器是依赖于Servlet的,不依赖于Spring

方式2:自定义过滤器并通过FilterRegistrationBean实例注册,可以同时注册多个并且设置优先级

 

方式1实现过滤器

自定义过滤器,使用@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

由以上可以的得出以下几个结论

  • 注解@Order或者Ordered接口的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响
  • @Order注解+@Component注解可以控制过滤器的执行顺序,但是无法使用@WebFilter注解修改过滤器的名字和过滤url,可以看出@WebFilter初始化的过滤器会被Spring容器初始化的过滤器给覆盖(解释了为什么初始化2次,且过滤的url改变了)

 

方式2实现过滤器

主要是利用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<AuthFilter> RegistAuth(){
        //通过FilterRegistrationBean实例设置优先级可以生效
        //通过@WebFilter无效
        FilterRegistrationBean<AuthFilter> bean = new FilterRegistrationBean<Test1Filter>();
        bean.setFilter(new AuthFilter());//注册自定义过滤器
        bean.setName("AuthFilter");//过滤器名称
        bean.addUrlPatterns("/auth/*");//过滤所有路径
        bean.setOrder(1);//优先级,最顶级
        return bean;
    }
    @Bean
    public FilterRegistrationBean<TestFilter> RegistTest2(){
        //通过FilterRegistrationBean实例设置优先级可以生效
        //通过@WebFilter无效
        FilterRegistrationBean<TestFilter> bean = new FilterRegistrationBean<Test2Filter>();
        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.
*
* <p>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);
            }
        }
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值