Servlet、过滤器、监听器、拦截器

前言:Servlet是javaEE规范中的一种,javaEE中的规范很多除了Servlet还有很多我们熟悉的JSP、JDBC、RMI、XML、EJB、JTS等等。他们每个都有自己不同的角色,每一种规范在企业级java应用中都承担了不可或缺的角色。Servlet是Service + Applet的缩写,表示小服务程序。从命名就可以看出他是被用来书写服务端程序的。但是在这个很讲究开发效率的年代已久见不到原生的Servlet程序写的服务端程序了。几乎主流的框架都对他进行了封装,比如SpringMVC便是如此,比如SpringMVC的核心DispatcherServlet,SpringMVC正是基于他来将请求进行解析、适配、映射的最后到达我们想要调用的接口中。

一、Servlet

1.Servlet是什么

Servlet = Service + Applet,表示小服务程序,他是javaEE中的一种规范,狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中比如Tomcat。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

2.Servlet的作用

从上面的介绍我们可以看出,Servlet的作用就是写服务端程序的,这是没有任何问题的,在开发者的角度这就是Servlet的作用,此外Servlet其他的作用都是相对于服务器来说的,他指定了Http服务器管理动态资源文件的规则等。java中所有的前端发送的请求都必须经过Servlet接口的实现类。我们使用的主流框架中比如SpringBoot、SpringCloud都看不到Servlet的身影,那是因为他们内置的SpringMVC中对Servlet都进行了封装。

3.ServletDemo

下面展示一个Servlet程序的写法,首先需要使用IDEA创建一个javaEE的工程,
第一步:选择Module来进行创建。
Servlet、过滤器、监听器、拦截器-小白菜博客
第二步:选择java企业版,然后勾选web应用即可,剩余的部分都是自己随便填好了,这样我们就可以创建出一个Servlet的应用了。
Servlet、过滤器、监听器、拦截器-小白菜博客
第三步:创建一个java类继承Servlet接口,并重写doGet、doPost方法。

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Servlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("执行了post请求。。。");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("执行了get请求。。。");
    }


}

第四步:在web.xml中配置Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>Servlet</servlet-name><!--Servlet起个别名 -->
        <servlet-class>com.cn.controller.Servlet</servlet-class><!-- Servlet的全限定名 -->
    </servlet>
    <servlet-mapping>
        <servlet-name>Servlet</servlet-name><!-- 上方起的别名在下面为他配映射地址 -->
        <url-pattern>/test</url-pattern><!-- 为该别名配置地址,相当于我们在注解RequestMapping中配地址 -->
    </servlet-mapping>
</web-app>

到这里为止,一个简单的Servlet就开发完了,剩下的就是部署测试了,我们需要将这个程序打成war包发布到Tomcat上,IDEA已经帮我们打好了包,我们只需要将该包加载进Tomcat即可,值得注意的是我们最好服务名从新配置一个,不然会有些长,就是图中所示位置
Servlet、过滤器、监听器、拦截器-小白菜博客
这一切都准备完了,我们就可以来开始启动Tomcat访问了,启动后自动弹出地址,我们加上自己配的test即可访问到刚刚的Servlet了。展示如下,可以看到IDEA中正常输出了“执行了get请求。。。”,地址栏请求都是get请求,因此我们程序是没有问题的,这样就完成了简单的前后端交互,说明这个Servlet没有问题。
在这里插入图片描述

4.Servlet的生命周期

说Servlet就必须提的是Servlet的声明周期,Servlet默认是在有请求到达时才会创建,可以通过配置实现在Http服务器启动时就启动,且默认也都是单利模式。然后会调用Servelt的init方法,然后就是service方法,然后service会根据是get还是post请求去调用不同的子类方法,最后在Http服务器关闭时会调用distroy方法销毁Servlet的实例。这里还涉及到了一个设计模式的使用即模板模式,我们继承HttpServlet时只要重写doGet、doPost等方法,是因为在HttpServlet中会根据请求的不同去分别调用不同的方法,我们只需要提供方法的实现就可以,逻辑都是确定的了,这便是模板模式。
生命周期:创建-init----->service----->doGet(doPost)----->distroy

二、过滤器Filter、监听器Listener

1.过滤器

Servlet是javaEE的一种规范,而过滤器Filter可以看成是Servlet的一种规范,过滤器的作用主要是过滤请求地址,过滤请求参数的信息,过滤器在实际开发中最常用的就是Xss过滤(Xss过滤就是将报文中可能产生危害的信息进行替换)。

2.监听器

与过滤器类似,监听器也是Servlet的一种规范,我们可以将它们都看出是Servlet,因为他们的实现机制都是一样的,但是监听器的用途就比较单一了。监听器主要用于监控作用域以及作用域中属性的变化。监听器总共有8种,与过滤器类似,他们都是接口都需要我们自己实现。下面列出8种监听器接口:

监听作用域的声明周期的监听器:
	ServletContextListener
	ServletRequestListener
	HttpSessionListener
监听作用域属性变化的监听器:
	ServletContextAttributeListener
	ServletRequestAttributeListener
	HttpSessionAttributeListener
监听Session的活化与钝化
	HttpSessionActivationListener
监听Session与对象的绑定
	HttpSessionBindingListener

3.过滤器的示例Demo

3.1实现Filter接口

写一个类继承Filter,然后重写doFilter方法即可,init和destroy重不重写没有关系

public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进入到过滤器1");
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("退出了过滤器1");
    }

    @Override
    public void destroy() {
        System.out.println("销毁");
    }
}

3.2配置web.xml

在web.xml中配置该Filter,如下,这样就完成了Filter的开发。

	<filter>
        <filter-name>LoginFilter</filter-name>
        <filter-class>com.cn.filter.LoginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoginFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3.3 展示测试结果

因为过滤器中只是打印了进入和退出,就直接放行了,所以正常就会输出进入过滤器->进入请求->退出过滤器,下面来看下结果也正如预测那样。
在这里插入图片描述

3.4多个Filter时,过滤器是如何执行的

这里就不展示了,我们可以看到一个过滤器时请求到达服务端是先进入过滤器,再到达接口,接口处理完再经过过滤器退出服务器的。多个过滤器时其实就是对接口进行套娃,或者说对多虑的内容进行套娃。画个丑图理解下:
Servlet、过滤器、监听器、拦截器-小白菜博客
如上图所示一个请求想要到达接口需要穿过所有的过滤器,进入过滤时的顺序和退出时恰恰是相反的,整个流程应该是Filter1—>Filter2—>Filter3—>进入接口执行完毕—>Filter3—>Filter2—>Filter1。这样一看这个流程是不是就像个俄罗斯套娃呢,最里面的就相当于接口,他被层层过滤器所包裹。上面所说的是多个过滤器的执行顺序固定后就是这么处理的,多个处理器到底应该先执行哪一个则是根据他们在web.xml中的配置顺序决定的。

4.比较监听器与过滤器

监听器与过滤器的实现没啥区别,就不一一赘述了,说下监听器与过滤器的区别,监听器主要作用就是监听作用域的,所有监听器都是为了监听作用域,再无他用,不能说应用场景很少,只能说开发中很少用到,写框架应该会经常用到,开发中用的确实不多,而过滤器的作用就很明显了,基本上每个项目都是必不可少的,我们可以通过过滤器对请求和响应都设置一些通用的东西,或者过滤掉有安全隐患的内容,以上是他们作用的不同,他们的相同点就是都是Servlet的规范的实现。

三、拦截器interceptor

说到过滤器和监听器不提拦截器是不合适的,因为他们是java里面的三大器,这三大器各司其职共同保证了程序的稳定运行。每当提起其中一个都会自然而然想到另外两个,那下面我们就来总结下拦截器。

1.拦截器是什么

拦截器我们使用时基本都是使用框架已经实现好的拦截器,基本不会自己写,现在常用的框架中SpringBoot、SpringCloud中我们可以去继承HandlerInterceptorAdapter类来去重写里面的preHandle、postHandle、afterHandle三个方法,如下所示:

public class LoginInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器预处理");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器返回之前执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器返回之后执行");
    }
}

此外我们还需要将这个拦截器交给Spring容器去管理,还需要为该拦截器配置拦截路径和排除路径,如下:

@Configuration
public class InterceptorConfiguration extends WebMvcConfigurerAdapter {

    @Bean
    public LoginInterceptor getLoginInterceptor(){
        return new LoginInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getLoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/find");
    }
}

这样我们就完成了拦截器的开发了,如果你使用的是最新版本的SpringBoot和SpringCloud你会发现,WebMvcConfigurerAdapter 和HandlerInterceptorAdapter 都已经被弃用了。他们的实现类上都被加了Deprecated这个注解,最新的实现方式已经不使用者两个类了,而是变成了两个接口,但是对于使用者来说实现的方式还是完全一样,对使用影响很小,最新版我们分别使用这两个接口就可以,操作什么的都还是一样:
WebMvcConfigurer、HandlerInterceptor,此外这里值得提一句的是配置过滤路径是我们一定要写成/**而不能写成/*,前者代表拦截所有请求,后者则只拦截根路径下的请求,在真实项目中使用后者基本没有意义。

2.拦截器的原理

与过滤器和监听器不同的是,拦截器的实现不是Servlet,而是通过java的动态代理来实现的。java的动态代理相信基本都会知道,如果不清楚可以翻下资料,拦截器正是使用的java的动态代理机制,来对接口执行前后来进行处理的,从而达到了对接口进出拦截的目的。

3.多个拦截器是如何执行的

前面说过多个过滤器的执行顺序就像是套娃,那拦截器呢,拦截器也是一样多个拦截器的执行也和俄罗斯套娃一样,图就不重复画了。值得注意的一点是postHandle是在方法返回之前执行的。

4.多个拦截器的执行顺序

我们在addInterceptors中可以配置多个拦截器,在该方法中配置拦截器的顺序就是拦截器执行的顺序,假如有如下的代码,那就是先执行CSRFInterceptor再执行LoginInterceptor。

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getCSRFInterceptor())
                .addPathPatterns("/**");
        registry.addInterceptor(getLoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/find");
        
    }

5.拦截器的应用场景

拦截器常被用于拦截CSRF()的请求,还经常被应用于登录拦截,验证用户是否已经登录。这些都是比较常用的。

四、过滤器和拦截器

上面已经总结完了,过滤器、监听器、拦截器。其中监听器作用比较单一仅是被用作监听作用域(这里只讨论Servlet提供的原生监听接口),但是过滤器和拦截器好像都可以拦截用户发出的请求,那他们有什么区别呢。

  • 区别:
  • 1.实现原理不同,拦截器是动态代理,过滤器是Servlet
  • 2.作用点不一样,过滤器因为是Servlet所以请求肯定先到达过滤器才能到达拦截器。
  • 3.拦截器除了可以处理响应前后的数据,还可以对返回之前的数据进行处理,这得益于java的动态代理。
  • 相同点
  • 1.都可以对请求响应前后进行处理。

有人说过滤器可以过滤所有请求,而拦截器不可以拦截所有请求,这里笔者不敢苟同,他们虽然实现机制不一样,但是都是可以拦截到所有请求的,只是拦截的位置不一样,过滤器相当于第一层、然后才会走到Servelt(可以看成DispatherServlet)再然后才到拦截器最后才能进入到接口,其实他们都可以对所有请求进行拦截,只不过过滤器的拦截点更靠前,所以对于安全性要求较高的拦截还是应该使用过滤器来处理。下面画个丑图来展现下过滤器和拦截器。

在这里插入图片描述