• @WebFilter 的使用及采坑


    @WebFilter
    @WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 ):

    表 3. @WebFilter 的常用属性

    下面是一个简单的示例:

    @WebFilter(servletNames = {"SimpleServlet"},filterName="SimpleFilter") 
    public class LessThanSixFilter implements Filter{...}

    如此配置之后,就可以不必在 web.xml 中配置相应的 <filter> 和 <filter-mapping> 元素了,容器会在部署时根据指定的属性将该类发布为过滤器。它等价的 web.xml 中的配置形式为:

    <filter> 
      <filter-name>SimpleFilter</filter-name> 
      <filter-class>xxx</filter-class> 
    </filter> 
    <filter-mapping> 
      <filter-name>SimpleFilter</filter-name> 
      <servlet-name>SimpleServlet</servlet-name> 
    </filter-mapping>

    由上文可知,使用@WebFilter相当于配置了web.xml,现在用eclipse自动生成Filter时,默认是提供这个注解的,如下所示

    package webcase;
     
    import java.io.IOException;
     
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
     
    /**
     * Servlet Filter implementation class CountFilter
     */
    @WebFilter("/CountFilter")
    public class CountFilter implements Filter {
        private int count;
        private String param;
        private FilterConfig fConfig;
        /**
         * Default constructor.
         */
        /*public CountFilter() {
            // TODO Auto-generated constructor stub
        }*/
     
        /**
         * @see Filter#destroy()
         */

    我们知道,tomcat根据<filter-mapping>的顺序初始化Filter,由于上面的代码包含@WebFilter("/CountFilter"),相当于在web.xml中对同一个过滤器CountFilter设置了两次<filter>和<filter-mapping>,故这个过滤器会初始化两次,故当@WebFilter设置的过滤器被初始化时,String param=getInitParameter("count")得到的字符串为空,调用Integer.parseInt(param)时引发NumberFormatException异常。去掉@WebFilter("/CountFilter")后则一切正常。

    采坑记录:

    业务需求背景:
    项目采用微服务架构,在各个服务前面配置一个网关,通过SpringCloud生态中的Zuul组件实现。
    该网关同时负责页面调度,在各个单页面应用子产品的页面之间进行调度。
    ZuulFilter挺有意思,对于本服务的Controller请求不会进行拦截,因此需要针对页面请求做一个认证鉴权的Filter。

    实现第一版
    首先实现一个Filter进行鉴权及页面重定向(未登录认证状态下跳转到登录页面)。
    大体逻辑如下:
    ①通过WebFilter进行Filter声明,这样容器在进行部署的时候就会处理该Filter,创建实例并创建配置对象FilterConfig,然后会将该Filter应用到urlPatterns所指定的url;
    ②在init方法中获取到初始化参数,自定义的excludedUrls,作为成员在后续执行过滤逻辑的时候使用;
    ③在doFilter中进行url的鉴定,如果需要执行认证鉴权处理,则执行相应逻辑。不满足条件的情况下重定向到登录页;
    ④Filter类增加Component注解,让该Filter被容器管理。

    @Component //这样加对吗?
    @WebFilter(filterName = "WebAuthFilter", urlPatterns = "/web/*",
            initParams = {
                @WebInitParam(name = "excludedUrls", value = "/web/login")
            }
    )
    public class WebAuthFilter implements Filter {
    
        private List<String> excludedUrlList;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            String excludedUrls = filterConfig.getInitParameter("excludeUrls");
            excludedUrlList = Splitter.on(",").omitEmptyStrings().splitToList(excludedUrls);
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            String url = ((HttpServletRequest) request).getRequestURI();
            if (excludedUrlList.contains(url)) {
                chain.doFilter(request, response);
            } else {
                String sToken = ((HttpServletRequest) request).getHeader("Authorization");
                if (sToken != null) {
                    Map<String, Object> map = TokenUtils.parseToken(sToken);
                    if (map == null) {
                        ((HttpServletResponse)response).sendRedirect("/web/login");
                    }
                } else {
                    ((HttpServletResponse)response).sendRedirect("/web/login");
                }
            }
        }
    
        @Override
        public void destroy() {
    
        }
    }

    然后在SpringBoot的Application中增加注解@ServletComponentScan,这样容器会扫描到@Component注解的Filter。

    问题出现
    出现的问题是:访问的url为/user/*或者/product/*的时候,该过滤器也执行了!
    也就是说,WebFilter注解配置的urlPatterns没有起作用。
    问题定位:
    在查看容器启动日志的时候,发现WebAuthFilter被两次注册,两次映射:

    从上图可以看到,WebAuthFilter这个Filter是我们自己定义的,它被做了两次映射,而且两次映射的名字不同(WebAuthFilter和webAuthFilter),分别映射到的URL是“/web/*”和”“/*”。其中WebAuthFilter是我们自己命名的。
    这样就解释了为什么所有的URL都会被该Filter处理。

    问题定位
    WebAuthFilter的第一次映射容易理解,是我们自己通过@WebFilter定义的。
    那么webAuthFilter是谁给映射的呢?
    必然是Spring容器处理的。
    在跟踪源码的时候找到AbstractFilterRegistrationBean抽象类,该类中有一个方法onStartup,应该是容器启动的时候执行的,做的是一些Bean注册的工作。该方法最后调用了configure,在该方法中进行了映射处理。

    if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
                this.logger.info("Mapping filter: '" + registration.getName() + "' to: "
                        + Arrays.asList(DEFAULT_URL_MAPPINGS));
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                        DEFAULT_URL_MAPPINGS);
            }
            else {
                if (!servletNames.isEmpty()) {
                    this.logger.info("Mapping filter: '" + registration.getName()
                            + "' to servlets: " + servletNames);
                    registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
                            servletNames.toArray(new String[servletNames.size()]));
                }
                if (!this.urlPatterns.isEmpty()) {
                    this.logger.info("Mapping filter: '" + registration.getName()
                            + "' to urls: " + this.urlPatterns);
                    registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
                            this.urlPatterns.toArray(new String[this.urlPatterns.size()]));
                }
            }

    在servletNames和urlPatterns为空的情况下,进行了缺省映射,即映射到“/*”。
    置于servletNames和urlPatterns为空的情况,这里没有深究了。
    那么,为什么会出现定义的WebAuthFilter被两次注册的情况呢?
    仔细分析了一下,认为可能的原因是:@Component和@WebFilter双重注册导致的。

    解决办法
    解决办法一@WebFilter
    在这种情况下,去掉了@Component注解,再次启动服务。查看日志,发现该Filter仅被映射一次,通过浏览器访问相应的url也表现正确。

    解决办法二@Component
    这种情况下,保留了@Component注解,那么要进行配置的urlPatterns怎么处理呢?
    通过FilterRegistrationBean进行@Bean声明,查看源码知道,onStartup进行注册的时候,实际上也是找到了各类RegistrationBean然后分别注册,配置映射。
    有各种类型的RegistrationBean:
    ①AbstractFilterRegistrationBean;
    ②FilterRegistrationBean;
    ③ServletListenerRegistrationBean;
    ④ServletRegistrationBean;
    那么我们自然可以通过自声明一个FilterRegistrationBean来进行注册。这种处理方式如下:
    去掉FIlter上的@WebFilter注解,增加如下的Configuration类:

    @Configuration
    public class WebAuthFilterConfig {
    
        @Bean
        public FilterRegistrationBean webAuthFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(webAuthFilter());
            registration.setName("WebAuthFilter");
            registration.addUrlPatterns("/web/*");
            registration.addInitParameter("excludeUrls", "/web/login");
            registration.setOrder(0);
            return registration;
        }
    
        @Bean
        public Filter webAuthFilter() {
            return new WebAuthFilter();
        }
    }

    如此处理,也能达到同样的效果。

    经过对比,当然第一种解决方案更直白,更简洁。

    后述:网上的很多东西都是带着坑的,直接搬过来用真的有风险!

    参考链接:

    https://blog.csdn.net/weixin_42114097/article/details/81530628

    https://blog.csdn.net/achang07/article/details/79282789

  • 相关阅读:
    AirtestProject2airtest安装与连接模拟器 广深
    人生苦短我学Java11常用类及方法 广深
    linux4更改系统时间时区为Shanghai 广深
    人生苦短我学Java12异常处理/集合/ArrayList与LinkedList/队列/堆栈/链表 广深
    人生苦短我学Java14HashSet/Map等实现类 广深
    Windows后台运行python等程序/开机自启程序 广深
    AirtestProject1airtest框架简介 广深
    人生苦短我学Java13泛型/Set集合/二叉树 广深
    jenkins5使用http请求远程构建Job 广深
    ThinkPHP distinct count 统计查询写法
  • 原文地址:https://www.cnblogs.com/muxi0407/p/11950627.html
Copyright © 2020-2023  润新知