Servlet中的Filter和Listener

648 阅读5分钟

javaweb三大组件,前面已经大致介绍了Servlet,直白说他实际就是我们用来处理具体业务逻辑的某个class,需要通过一定的配置和规则才能访问到,然后本篇再来看看另外两个组件。

Filter

先看看Filter的定义吧。
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
这是java doc上对他的定义,他是用来执行任意一个请求和响应的过滤和拦截任务,是一种可以重用的技术,可以作用于动态和静态资源,可以将一个或多个filter组成的Filter链加入容器中,而且一个Filter链是可以中断的,只要执行doFilter() 方法就不会进入到下一个链。
直白说就是任何一个请求到达servlet之前可以经过一个或多个Filter,支持修改请求头等信息,当然也可以做一些其他的事,一般按功能可以大致分为认证(比如校验身份),安全(比如xss拦截),日志埋点,数据加密,设置编码等前置工作。任何一个响应在回到客户端之前可能经过一个或多个Filter,比如记录请求消耗时间等。

default public void init(FilterConfig filterConfig) throws ServletException {}

public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain)
        throws IOException, ServletException;

default public void destroy() {}

init方法参考Servlet的init()方法,实现原理是一样的,都是由容器调用,然后容器会根据具体的配置,比如什么初始化参数来生成一个具体的FilterConfig传进来,关于这个init的执行顺序。 doFilter就是我们的具体需要操作的细节实现,一般我们需要声明一个Filter的时候只需要重写doFilter就可以满足了,看个例子

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("logFilter init...");
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            System.out.println("http request");
   // 在该Filter处理完了放行,如果不满意这个请求就不调用这个方法终止这次请求了
            chain.doFilter(request, response);
        }

    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

简单声明一个打印日志的Filter,当然实际情况肯定不会这么简单,其区别就在于到时候的doFilter实现会复杂一些,比如会识别一下uri,httpmethod,持久化等等,但总体流程都是一样的。
需要注意的是,我们可以为一个Filter指定他的作用范围,比如指定到达某些Servlet的请求或者某些静态资源才该Filter才会拦截,在web.xml中这一功能是通过<filter-mapping>来实现的,但是一个<filter-mapping>中的<url—pattern>标签只能指定一个匹配规则,所以可以声明多个<filter-mappng>来声明多个匹配规则,只要他们的name指向同一个Filter就行,像这样

<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>me.ppx.mvc.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>logFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>logFilter</filter-name>
  <url-pattern>*.do</url-pattern>
</filter-mapping>

但是为了让大家熟悉没有web.xml的日子,我还是准备通过动态注册的方式来注册该Filter

public class AnimalServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) {
// 手动注册servlet
        ServletRegistration.Dynamic servlet = ctx.addServlet("initializerServlet", new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                System.out.println("这是编程方式注册的");
            }
        });
        servlet.addMapping("/init");
//        手动注册Filter
        FilterRegistration.Dynamic filter = ctx.addFilter("logFilter", new LogFilter());
       //DispatcherType.REQUEST表示直接来自客户端的请求 filter.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST),true,"initializerServlet");
//        filter.addMappingForUrlPatterns();
    }
}

使用ServletContext注册自己的Filter,同时使用addMappingForServletNames方法注册匹配规则,该方法是通过指定servlet的名字,可以写多个,他接受的是个不定长的字符串参数,当然也可以使用addMappingForUrlPatterns方法注册匹配规则,他是通过指定url,是个集合类型的参数,使用哪种就看自己需要
另外,servlet3.1规范声明service 方法必须和应用到 servlet 的所有过滤器运行在同一个线程中。

Listener

其实listener应该算是最好理解的,直白点就是web容器里发生了什么事,然后通知Listener,我们重写Listener的方法就能在自己感兴趣的事情发生后干点别的。Sevlet3.1规范中明确指定了几种Listener,我选其中一种对后续理解有帮助的介绍一下。

  • ServletContextListener
    他是一个生命周期相关的事件,发生在Servlet上下文刚刚创建好,该方法执行完之后才会执行Filter的init方法,或者servlet上下文即将关闭
public interface ServletContextListener extends EventListener {
     //刚刚创建好 
    default public void contextInitialized(ServletContextEvent sce) {}
    //即将关闭
    default public void contextDestroyed(ServletContextEvent sce) {}
}

contextInitialized方法会在任何Filter和Servlet初始化之前被执行,还记得上文说的Servlet规范明确指定了如果要通过编程的方式注册Servlet和Filter只能ServletContainerInitializer的onStartup方法中注册或者ServletContainerInitializercontextInitialized中注册,前面只演示了第一种,现在就可以演示第二种了

public class PPXServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("context init...");
        ServletContext context = sce.getServletContext();
        ServletRegistration.Dynamic servlet = context.addServlet("ppxlistener", new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                System.out.println("listener被请求了");
            }

            @Override
            public void init() throws ServletException {
                System.out.println("在ServletContextListener中注册");
            }
        });
        servlet.setLoadOnStartup(3);
        servlet.addMapping("/listen");

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContextListener.super.contextDestroyed(sce);
    }
}

然后还需要在web.xml中配置该实现类的全类名就好了,是不是被发现现在开始我们的web.xml里面几乎没有任何东西了,到目前为止除了这个listener需要配置进去,其他的都不需要了,这就算是在一步步演进吧

<listener>
    <listener-class>me.ppx.mvc.initializer.listener.PPXServletContextListener</listener-class>
</listener>

然后直接启动容器就ok了。注意的是在web.xml中,listener>filter>servlet,不然会报错。通过编程的方式注册的话这些就不用操心了,所以这也算是为了方便我们这些开发者吧

总结

servlet相关的介绍就到此结束了,我本意并不是想为大家详细的介绍Servlet的具体使用和各种细节,而是想通过这种方式给大家一个印象,提醒大家最早的web开发是怎么样的,哪怕现在不会直接再使用Servlet进行web开发了,也需要去了解他的核心特性,这对我们后续理解springmvc,springboot是有很大帮助的,毕竟他们都是在这个基础上进行优化。本人能力有限,如果有错误的地方还请各位评论指正,谢谢。